diff --git a/docs/docs/faqs/liquid-markets.mdx b/docs/docs/faqs/liquid-markets.mdx
index d92491833..587bcfaa6 100644
--- a/docs/docs/faqs/liquid-markets.mdx
+++ b/docs/docs/faqs/liquid-markets.mdx
@@ -31,6 +31,8 @@ import EthAptos from './_liquid-markets/eth-aptos.md'
import EthAlgorand from './_liquid-markets/eth-algorand.md'
import SolAlgorand from './_liquid-markets/sol-algorand.md'
import AvaxAlgorand from './_liquid-markets/avax-algorand.md'
+import SolInjective from './_liquid-markets/sol-injective.md'
+import EthInjective from './_liquid-markets/eth-injective.md'
import EthSui from './_liquid-markets/eth-sui.md'
import SolSui from './_liquid-markets/sol-sui.md'
import AptosOsmosis from './_liquid-markets/aptos-osmosis.md'
@@ -199,6 +201,17 @@ Check out the [Wormhole Token list](https://github.com/certusone/wormhole-token-
+## Target chain: Injective
+
+
+
+
+
+
+
+
+
+
## Target chain: Sui
diff --git a/package-lock.json b/package-lock.json
index 29d4d7146..1dc21e4d9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,10 @@
"@certusone/wormhole-sdk": "^0.10.17",
"@cosmjs/cosmwasm-stargate": "^0.32.3",
"@cosmjs/tendermint-rpc": "^0.32.3",
+ "@injectivelabs/networks": "^1.14.4",
+ "@injectivelabs/sdk-ts": "^1.14.4",
+ "@injectivelabs/ts-types": "^1.14.4",
+ "@injectivelabs/wallet-ts": "^1.14.4",
"@manahippo/aptos-wallet-adapter": "^1.0.2",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.11.2",
diff --git a/package.json b/package.json
index 3e5252969..bc472a67d 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,10 @@
"@certusone/wormhole-sdk": "^0.10.17",
"@cosmjs/cosmwasm-stargate": "^0.32.3",
"@cosmjs/tendermint-rpc": "^0.32.3",
+ "@injectivelabs/networks": "^1.14.4",
+ "@injectivelabs/sdk-ts": "^1.14.4",
+ "@injectivelabs/ts-types": "^1.14.4",
+ "@injectivelabs/wallet-ts": "^1.14.4",
"@manahippo/aptos-wallet-adapter": "^1.0.2",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.11.2",
diff --git a/src/components/KeyAndBalance.tsx b/src/components/KeyAndBalance.tsx
index f88fa2d2d..bbe761af1 100644
--- a/src/components/KeyAndBalance.tsx
+++ b/src/components/KeyAndBalance.tsx
@@ -2,6 +2,7 @@ import {
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_XPLA,
@@ -21,6 +22,7 @@ function isChainAllowed(chainId: ChainId) {
chainId === CHAIN_ID_NEAR ||
chainId === CHAIN_ID_XPLA ||
chainId === CHAIN_ID_APTOS ||
+ chainId === CHAIN_ID_INJECTIVE ||
chainId === CHAIN_ID_SUI ||
chainId === CHAIN_ID_SEI
);
diff --git a/src/components/Recovery.tsx b/src/components/Recovery.tsx
index 15aba528d..cce679ddb 100644
--- a/src/components/Recovery.tsx
+++ b/src/components/Recovery.tsx
@@ -3,6 +3,7 @@ import {
CHAIN_ID_ACALA,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_KARURA,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
@@ -11,6 +12,7 @@ import {
CHAIN_ID_SEI,
getEmitterAddressAlgorand,
getEmitterAddressEth,
+ getEmitterAddressInjective,
getEmitterAddressSolana,
getEmitterAddressTerra,
getEmitterAddressXpla,
@@ -23,12 +25,14 @@ import {
parseNFTPayload,
parseSequenceFromLogAlgorand,
parseSequenceFromLogEth,
+ parseSequenceFromLogInjective,
parseSequenceFromLogSolana,
parseSequenceFromLogTerra,
parseSequenceFromLogXpla,
parseTransferPayload,
parseVaa,
queryExternalId,
+ queryExternalIdInjective,
TerraChainId,
uint8ArrayToHex,
CHAIN_ID_SUI,
@@ -111,6 +115,10 @@ import {
getEmitterAddressAndSequenceFromResult,
} from "../utils/aptos";
import { Types } from "aptos";
+import {
+ getInjectiveTxClient,
+ getInjectiveWasmClient,
+} from "../utils/injective";
import { getSuiProvider } from "../utils/sui";
import {
getEmitterAddressAndSequenceFromResponseSui,
@@ -377,6 +385,26 @@ async function xpla(tx: string, enqueueSnackbar: any) {
}
}
+async function injective(txHash: string, enqueueSnackbar: any) {
+ try {
+ const client = getInjectiveTxClient();
+ const tx = await client.fetchTx(txHash);
+ if (!tx) {
+ throw new Error("Unable to fetch transaction");
+ }
+ const sequence = parseSequenceFromLogInjective(tx);
+ if (!sequence) {
+ throw new Error("Sequence not found");
+ }
+ const emitterAddress = await getEmitterAddressInjective(
+ getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE)
+ );
+ return await fetchSignedVAA(CHAIN_ID_INJECTIVE, emitterAddress, sequence);
+ } catch (e) {
+ return handleError(e, enqueueSnackbar);
+ }
+}
+
async function sui(digest: string, enqueueSnackbar: any) {
try {
const provider = getSuiProvider();
@@ -621,6 +649,21 @@ export default function Recovery() {
}
})();
}
+ if (parsedPayload && parsedPayload.targetChain === CHAIN_ID_INJECTIVE) {
+ (async () => {
+ const client = getInjectiveWasmClient();
+ const tokenBridgeAddress =
+ getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE);
+ const tokenId = await queryExternalIdInjective(
+ client as any,
+ tokenBridgeAddress,
+ parsedPayload.originAddress
+ );
+ if (!cancelled) {
+ setTokenId(tokenId || "");
+ }
+ })();
+ }
if (parsedPayload && parsedPayload.targetChain === CHAIN_ID_SUI) {
(async () => {
@@ -802,6 +845,26 @@ export default function Recovery() {
setIsVAAPending(isPending);
}
})();
+ } else if (recoverySourceChain === CHAIN_ID_INJECTIVE) {
+ setRecoverySourceTxError("");
+ setRecoverySourceTxIsLoading(true);
+ setTokenId("");
+ (async () => {
+ const { vaa, isPending, error } = await injective(
+ recoverySourceTx,
+ enqueueSnackbar
+ );
+ if (!cancelled) {
+ setRecoverySourceTxIsLoading(false);
+ if (vaa) {
+ setRecoverySignedVAA(vaa);
+ }
+ if (error) {
+ setRecoverySourceTxError(error);
+ }
+ setIsVAAPending(isPending);
+ }
+ })();
} else if (recoverySourceChain === CHAIN_ID_SUI) {
setRecoverySourceTxError("");
setRecoverySourceTxIsLoading(true);
@@ -1166,7 +1229,8 @@ export default function Recovery() {
value={
parsedPayload
? parsedPayload.targetChain === CHAIN_ID_TERRA2 ||
- parsedPayload.targetChain === CHAIN_ID_XPLA
+ parsedPayload.targetChain === CHAIN_ID_XPLA ||
+ parsedPayload.targetChain === CHAIN_ID_INJECTIVE
? tokenId
: hexToNativeAssetString(
parsedPayload.originAddress,
diff --git a/src/components/ShowTx.tsx b/src/components/ShowTx.tsx
index 1954644da..e7f35b881 100644
--- a/src/components/ShowTx.tsx
+++ b/src/components/ShowTx.tsx
@@ -19,6 +19,7 @@ import {
CHAIN_ID_XPLA,
CHAIN_ID_APTOS,
CHAIN_ID_ARBITRUM,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_OPTIMISM,
CHAIN_ID_SUI,
CHAIN_ID_BASE,
@@ -168,6 +169,10 @@ export default function ShowTx({
? `https://${
CLUSTER === "testnet" ? "goerli." : ""
}arbiscan.io/tx/${tx?.id}`
+ : chainId === CHAIN_ID_INJECTIVE
+ ? `https://${
+ CLUSTER === "testnet" ? "testnet." : ""
+ }explorer.injective.network/transaction/${tx.id}`
: chainId === CHAIN_ID_OPTIMISM
? `https://${
CLUSTER === "testnet" ? "goerli-optimism." : "optimistic."
diff --git a/src/components/SmartAddress.tsx b/src/components/SmartAddress.tsx
index 27634d5bd..e8eb66290 100644
--- a/src/components/SmartAddress.tsx
+++ b/src/components/SmartAddress.tsx
@@ -22,6 +22,7 @@ import {
CHAIN_ID_APTOS,
isValidAptosType,
CHAIN_ID_ARBITRUM,
+ CHAIN_ID_INJECTIVE,
terra,
CHAIN_ID_OPTIMISM,
CHAIN_ID_SUI,
@@ -229,6 +230,14 @@ export default function SmartAddress({
? `https://${CLUSTER === "testnet" ? "goerli." : ""}arbiscan.io/${
isAsset ? "token" : "address"
}/${useableAddress}`
+ : chainId === CHAIN_ID_INJECTIVE
+ ? `https://${
+ CLUSTER === "testnet" ? "testnet." : ""
+ }explorer.injective.network/${
+ isAsset
+ ? `asset/?tokenType=${isNative ? "native" : "cw20"}&tokenIdentifier=`
+ : "account/"
+ }${useableAddress}`
: chainId === CHAIN_ID_OPTIMISM
? `https://${
CLUSTER === "testnet" ? "goerli-optimism." : "optimistic."
diff --git a/src/components/TokenSelectors/InjectiveTokenPicker.tsx b/src/components/TokenSelectors/InjectiveTokenPicker.tsx
new file mode 100644
index 000000000..390411b37
--- /dev/null
+++ b/src/components/TokenSelectors/InjectiveTokenPicker.tsx
@@ -0,0 +1,168 @@
+import {
+ CHAIN_ID_INJECTIVE,
+ isNativeDenomInjective,
+ parseSmartContractStateResponse,
+} from "@certusone/wormhole-sdk";
+import { formatUnits } from "@ethersproject/units";
+import { useCallback, useMemo, useRef } from "react";
+import { createParsedTokenAccount } from "../../hooks/useGetSourceParsedTokenAccounts";
+import useIsWalletReady from "../../hooks/useIsWalletReady";
+import useInjectiveNativeBalances from "../../hooks/useInjectiveNativeBalances";
+import { DataWrapper } from "../../store/helpers";
+import { NFTParsedTokenAccount } from "../../store/nftSlice";
+import { ParsedTokenAccount } from "../../store/transferSlice";
+import TokenPicker, { BasicAccountRender } from "./TokenPicker";
+import {
+ formatNativeDenom,
+ getInjectiveWasmClient,
+ INJECTIVE_NATIVE_DENOM,
+ isValidInjectiveAddress,
+ NATIVE_INJECTIVE_DECIMALS,
+} from "../../utils/injective";
+import injectiveIcon from "../../icons/injective.svg";
+
+type InjectiveTokenPickerProps = {
+ value: ParsedTokenAccount | null;
+ onChange: (newValue: ParsedTokenAccount | null) => void;
+ tokenAccounts: DataWrapper | undefined;
+ disabled: boolean;
+ resetAccounts: (() => void) | undefined;
+};
+
+const returnsFalse = () => false;
+
+export default function InjectiveTokenPicker(props: InjectiveTokenPickerProps) {
+ const { value, onChange, disabled } = props;
+ const { walletAddress } = useIsWalletReady(CHAIN_ID_INJECTIVE);
+ const nativeRefresh = useRef<() => void>(() => {});
+ const { balances, isLoading: nativeIsLoading } = useInjectiveNativeBalances(
+ walletAddress,
+ nativeRefresh
+ );
+
+ const resetAccountWrapper = useCallback(() => {
+ //we can currently skip calling this as we don't read from sourceParsedTokenAccounts
+ //resetAccounts && resetAccounts();
+ nativeRefresh.current();
+ }, []);
+ const isLoading = nativeIsLoading; // || (tokenMap?.isFetching || false);
+
+ const onChangeWrapper = useCallback(
+ async (account: NFTParsedTokenAccount | null) => {
+ if (account === null) {
+ onChange(null);
+ return Promise.resolve();
+ }
+ onChange(account);
+ return Promise.resolve();
+ },
+ [onChange]
+ );
+
+ const injTokenArray = useMemo(() => {
+ const balancesItems =
+ balances && walletAddress
+ ? Object.keys(balances).map((denom) =>
+ //This token account makes a lot of assumptions
+ createParsedTokenAccount(
+ walletAddress,
+ denom,
+ balances[denom], //amount
+ NATIVE_INJECTIVE_DECIMALS, //TODO actually get decimals rather than hardcode
+ 0, //uiAmount is unused
+ formatUnits(balances[denom], NATIVE_INJECTIVE_DECIMALS), //uiAmountString
+ formatNativeDenom(denom), // symbol
+ undefined, //name
+ injectiveIcon,
+ true //is native asset
+ )
+ )
+ : [];
+ return balancesItems.filter(
+ (metadata) => metadata.mintKey === INJECTIVE_NATIVE_DENOM
+ );
+ }, [walletAddress, balances]);
+
+ //TODO this only supports non-native assets. Native assets come from the hook.
+ //TODO correlate against token list to get metadata
+ const lookupInjectiveAddress = useCallback(
+ (lookupAsset: string) => {
+ if (!walletAddress) {
+ return Promise.reject("Wallet not connected");
+ }
+ const client = getInjectiveWasmClient();
+ return client
+ .fetchSmartContractState(
+ lookupAsset,
+ Buffer.from(
+ JSON.stringify({
+ token_info: {},
+ })
+ ).toString("base64")
+ )
+ .then((infoData) =>
+ client
+ .fetchSmartContractState(
+ lookupAsset,
+ Buffer.from(
+ JSON.stringify({
+ balance: {
+ address: walletAddress,
+ },
+ })
+ ).toString("base64")
+ )
+ .then((balanceData) => {
+ if (infoData && balanceData) {
+ const balance = parseSmartContractStateResponse(balanceData);
+ const info = parseSmartContractStateResponse(infoData);
+ return createParsedTokenAccount(
+ walletAddress,
+ lookupAsset,
+ balance.balance.toString(),
+ info.decimals,
+ Number(formatUnits(balance.balance, info.decimals)),
+ formatUnits(balance.balance, info.decimals),
+ info.symbol,
+ info.name
+ );
+ } else {
+ throw new Error("Failed to retrieve Injective account.");
+ }
+ })
+ )
+ .catch((e) => {
+ return Promise.reject(e);
+ });
+ },
+ [walletAddress]
+ );
+
+ const isSearchableAddress = useCallback((address: string) => {
+ return isValidInjectiveAddress(address) && !isNativeDenomInjective(address);
+ }, []);
+
+ const RenderComp = useCallback(
+ ({ account }: { account: NFTParsedTokenAccount }) => {
+ return BasicAccountRender(account, returnsFalse, false);
+ },
+ []
+ );
+
+ return (
+
+ );
+}
diff --git a/src/components/TokenSelectors/SourceTokenSelector.tsx b/src/components/TokenSelectors/SourceTokenSelector.tsx
index d3e50f208..a744e913f 100644
--- a/src/components/TokenSelectors/SourceTokenSelector.tsx
+++ b/src/components/TokenSelectors/SourceTokenSelector.tsx
@@ -2,6 +2,7 @@
import {
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_SUI,
@@ -38,6 +39,7 @@ import RefreshButtonWrapper from "./RefreshButtonWrapper";
import SolanaTokenPicker from "./SolanaTokenPicker";
import TerraTokenPicker from "./TerraTokenPicker";
import XplaTokenPicker from "./XplaTokenPicker";
+import InjectiveTokenPicker from "./InjectiveTokenPicker";
import SuiTokenPicker from "./SuiTokenPicker";
import SeiTokenPicker from "./SeiTokenPicker";
@@ -160,6 +162,14 @@ export const TokenSelector = (props: TokenSelectorProps) => {
tokenAccounts={maps?.tokenAccounts}
nft={nft}
/>
+ ) : lookupChain === CHAIN_ID_INJECTIVE ? (
+
) : lookupChain === CHAIN_ID_SUI ? (
{
+ const isValidNetwork = INJECTIVE_NETWORKS.includes(
+ process.env.REACT_APP_CLUSTER || ""
+ );
+ if (!isValidNetwork) return [];
+
+ const network = getInjectiveNetworkName();
+ const networkEndpoints = getNetworkInfo(network);
+
+ const opts = {
+ networkChainId: getInjectiveNetworkChainId(),
+ broadcasterOptions: {
+ network,
+ networkEndpoints,
+ },
+ };
+
+ return [new KeplrWallet(opts)];
+};
+
+export interface IInjectiveContext {
+ wallet?: InjectiveWallet;
+ address?: string;
+}
+
+export const useInjectiveContext = (): IInjectiveContext => {
+ const wallet = useWallet(CHAIN_ID_INJECTIVE);
+
+ const address = useMemo(() => wallet?.getAddress(), [wallet]);
+
+ return useMemo(
+ () => ({
+ wallet,
+ address,
+ }),
+ [wallet, address]
+ );
+};
diff --git a/src/hooks/useCheckIfWormholeWrapped.ts b/src/hooks/useCheckIfWormholeWrapped.ts
index 8185ba063..6e616a6ad 100644
--- a/src/hooks/useCheckIfWormholeWrapped.ts
+++ b/src/hooks/useCheckIfWormholeWrapped.ts
@@ -16,6 +16,8 @@ import {
isTerraChain,
uint8ArrayToHex,
WormholeWrappedInfo,
+ CHAIN_ID_INJECTIVE,
+ getOriginalAssetInjective,
CHAIN_ID_SUI,
getOriginalAssetSui,
CHAIN_ID_ETH,
@@ -62,6 +64,7 @@ import {
import { getOriginalAssetNear, makeNearAccount } from "../utils/near";
import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
import { getAptosClient } from "../utils/aptos";
+import { getInjectiveWasmClient } from "../utils/injective";
import { getSuiProvider } from "../utils/sui";
import { getOriginalAssetSei, getSeiWasmClient } from "../utils/sei";
import { base58 } from "ethers/lib/utils";
@@ -297,6 +300,17 @@ function useCheckIfWormholeWrapped(nft?: boolean) {
}
} catch (e) {}
}
+ if (sourceChain === CHAIN_ID_INJECTIVE && sourceAsset) {
+ try {
+ const client = getInjectiveWasmClient();
+ const wrappedInfo = makeStateSafe(
+ await getOriginalAssetInjective(sourceAsset, client as any)
+ );
+ if (!cancelled) {
+ dispatch(setSourceWormholeWrappedInfo(wrappedInfo));
+ }
+ } catch (e) {}
+ }
if (sourceChain === CHAIN_ID_SUI && sourceAsset) {
try {
const wrappedInfo = makeStateSafe(
diff --git a/src/hooks/useFetchForeignAsset.ts b/src/hooks/useFetchForeignAsset.ts
index 1d949fc3a..9ab2efdc9 100644
--- a/src/hooks/useFetchForeignAsset.ts
+++ b/src/hooks/useFetchForeignAsset.ts
@@ -3,6 +3,7 @@ import {
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA2,
@@ -11,6 +12,7 @@ import {
getForeignAssetAlgorand,
getForeignAssetAptos,
getForeignAssetEth,
+ getForeignAssetInjective,
getForeignAssetSolana,
getForeignAssetTerra,
getForeignAssetXpla,
@@ -50,6 +52,7 @@ import {
import { useNearContext } from "../contexts/NearWalletContext";
import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
import { getAptosClient } from "../utils/aptos";
+import { getInjectiveWasmClient } from "../utils/injective";
import { getSuiProvider } from "../utils/sui";
import { getForeignAssetSei, getSeiWasmClient } from "../utils/sei";
@@ -224,6 +227,16 @@ function useFetchForeignAsset(
)
.catch(() => Promise.reject("Failed to make Near account"));
}
+ : foreignChain === CHAIN_ID_INJECTIVE
+ ? () => {
+ const client = getInjectiveWasmClient();
+ return getForeignAssetInjective(
+ getTokenBridgeAddressForChain(foreignChain),
+ client as any,
+ originChain,
+ hexToUint8Array(originAssetHex)
+ );
+ }
: foreignChain === CHAIN_ID_SUI
? () => {
return getForeignAssetSui(
diff --git a/src/hooks/useFetchTargetAsset.ts b/src/hooks/useFetchTargetAsset.ts
index 13962f1e4..84dc373dd 100644
--- a/src/hooks/useFetchTargetAsset.ts
+++ b/src/hooks/useFetchTargetAsset.ts
@@ -2,6 +2,7 @@ import {
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA2,
@@ -11,6 +12,7 @@ import {
getForeignAssetAlgorand,
getForeignAssetAptos,
getForeignAssetEth,
+ getForeignAssetInjective,
getForeignAssetSolana,
getForeignAssetTerra,
getForeignAssetXpla,
@@ -20,6 +22,7 @@ import {
isEVMChain,
isTerraChain,
queryExternalId,
+ queryExternalIdInjective,
CHAIN_ID_SUI,
getForeignAssetSui,
} from "@certusone/wormhole-sdk";
@@ -82,6 +85,7 @@ import {
} from "../utils/near";
import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
import { getAptosClient } from "../utils/aptos";
+import { getInjectiveWasmClient } from "../utils/injective";
import { getSuiProvider } from "../utils/sui";
import {
getForeignAssetSei,
@@ -279,6 +283,25 @@ function useFetchTargetAsset(nft?: boolean) {
);
}
}
+ } else if (originChain === CHAIN_ID_INJECTIVE) {
+ const client = getInjectiveWasmClient();
+ const tokenBridgeAddress =
+ getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE);
+ const tokenId = await queryExternalIdInjective(
+ client as any,
+ tokenBridgeAddress,
+ originAsset || ""
+ );
+ if (!cancelled) {
+ dispatch(
+ setTargetAsset(
+ receiveDataWrapper({
+ doesExist: true,
+ address: tokenId,
+ })
+ )
+ );
+ }
} else if (originChain === CHAIN_ID_SUI) {
const coinType = await getForeignAssetSui(
getSuiProvider(),
@@ -625,6 +648,36 @@ function useFetchTargetAsset(nft?: boolean) {
}
}
}
+ if (targetChain === CHAIN_ID_INJECTIVE && originChain && originAsset) {
+ dispatch(setTargetAsset(fetchDataWrapper()));
+ try {
+ const client = getInjectiveWasmClient();
+ const asset = await getForeignAssetInjective(
+ getTokenBridgeAddressForChain(targetChain),
+ client as any,
+ originChain,
+ hexToUint8Array(originAsset)
+ );
+ if (!cancelled) {
+ dispatch(
+ setTargetAsset(
+ receiveDataWrapper({ doesExist: !!asset, address: asset })
+ )
+ );
+ setArgs();
+ }
+ } catch (e) {
+ if (!cancelled) {
+ dispatch(
+ setTargetAsset(
+ errorDataWrapper(
+ "Unable to determine existence of wrapped asset"
+ )
+ )
+ );
+ }
+ }
+ }
if (targetChain === CHAIN_ID_SUI && originChain && originAsset) {
dispatch(setTargetAsset(fetchDataWrapper()));
try {
diff --git a/src/hooks/useGetIsTransferCompleted.ts b/src/hooks/useGetIsTransferCompleted.ts
index ee3c6034d..e6c60f52f 100644
--- a/src/hooks/useGetIsTransferCompleted.ts
+++ b/src/hooks/useGetIsTransferCompleted.ts
@@ -1,6 +1,7 @@
import {
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_SUI,
@@ -9,6 +10,7 @@ import {
getIsTransferCompletedAlgorand,
getIsTransferCompletedAptos,
getIsTransferCompletedEth,
+ getIsTransferCompletedInjective,
getIsTransferCompletedSolana,
getIsTransferCompletedSui,
getIsTransferCompletedTerra,
@@ -43,6 +45,7 @@ import useIsWalletReady from "./useIsWalletReady";
import useTransferSignedVAA from "./useTransferSignedVAA";
import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
import { getAptosClient } from "../utils/aptos";
+import { getInjectiveWasmClient } from "../utils/injective";
import { getSuiProvider } from "../utils/sui";
import { getIsTransferCompletedSei, getSeiWasmClient } from "../utils/sei";
@@ -240,6 +243,24 @@ export default function useGetIsTransferCompleted(
setIsLoading(false);
}
})();
+ } else if (targetChain === CHAIN_ID_INJECTIVE) {
+ setIsLoading(true);
+ (async () => {
+ try {
+ const client = getInjectiveWasmClient();
+ transferCompleted = await getIsTransferCompletedInjective(
+ getTokenBridgeAddressForChain(targetChain),
+ signedVAA,
+ client as any
+ );
+ } catch (error) {
+ console.error(error);
+ }
+ if (!cancelled) {
+ setIsTransferCompleted(transferCompleted);
+ setIsLoading(false);
+ }
+ })();
} else if (targetChain === CHAIN_ID_SUI) {
setIsLoading(true);
(async () => {
diff --git a/src/hooks/useGetSourceParsedTokenAccounts.ts b/src/hooks/useGetSourceParsedTokenAccounts.ts
index f0961c095..f9217b4c1 100644
--- a/src/hooks/useGetSourceParsedTokenAccounts.ts
+++ b/src/hooks/useGetSourceParsedTokenAccounts.ts
@@ -23,6 +23,7 @@ import {
ethers_contracts,
WSOL_ADDRESS,
WSOL_DECIMALS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_SUI,
CHAIN_ID_ARBITRUM,
CHAIN_ID_BASE,
@@ -2005,6 +2006,10 @@ function useGetAvailableTokens(nft: boolean = false) {
tokenAccounts,
resetAccounts: resetSourceAccounts,
}
+ : lookupChain === CHAIN_ID_INJECTIVE
+ ? {
+ resetAccounts: resetSourceAccounts,
+ }
: lookupChain === CHAIN_ID_SUI
? {
tokenAccounts,
diff --git a/src/hooks/useGetTargetParsedTokenAccounts.ts b/src/hooks/useGetTargetParsedTokenAccounts.ts
index 202fe3306..2d0a12f66 100644
--- a/src/hooks/useGetTargetParsedTokenAccounts.ts
+++ b/src/hooks/useGetTargetParsedTokenAccounts.ts
@@ -1,6 +1,7 @@
import {
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_SUI,
@@ -9,8 +10,10 @@ import {
ensureHexPrefix,
ethers_contracts,
isEVMChain,
+ isNativeDenomInjective,
isNativeDenomXpla,
isTerraChain,
+ parseSmartContractStateResponse,
terra,
} from "@certusone/wormhole-sdk";
import { Connection, PublicKey } from "@solana/web3.js";
@@ -49,6 +52,12 @@ import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
import { NATIVE_XPLA_DECIMALS } from "../utils/xpla";
import { useAptosContext } from "../contexts/AptosWalletContext";
import { getAptosClient } from "../utils/aptos";
+import {
+ getInjectiveBankClient,
+ NATIVE_INJECTIVE_DECIMALS,
+ getInjectiveWasmClient,
+} from "../utils/injective";
+import { useInjectiveContext } from "../contexts/InjectiveWalletContext";
import { useTerraWallet } from "../contexts/TerraWalletContext";
import { useSuiWallet } from "../contexts/SuiWalletContext";
import { getSuiProvider } from "../utils/sui";
@@ -80,6 +89,7 @@ function useGetTargetParsedTokenAccounts() {
const { address: algoAccount } = useAlgorandWallet();
const { accountId: nearAccountId } = useNearContext();
const { account: aptosAddress } = useAptosContext();
+ const { address: injAddress } = useInjectiveContext();
const seiWallet = useSeiWallet();
const seiAddress = seiWallet?.getAddress();
const suiWallet = useSuiWallet();
@@ -579,6 +589,87 @@ function useGetTargetParsedTokenAccounts() {
}
}
}
+ if (targetChain === CHAIN_ID_INJECTIVE && injAddress) {
+ if (isNativeDenomInjective(targetAsset)) {
+ const client = getInjectiveBankClient();
+ client
+ .fetchBalance({ accountAddress: injAddress, denom: targetAsset })
+ .then(({ amount }) => {
+ if (!cancelled) {
+ dispatch(
+ setTargetParsedTokenAccount(
+ createParsedTokenAccount(
+ "",
+ "",
+ amount,
+ NATIVE_INJECTIVE_DECIMALS,
+ Number(formatUnits(amount, NATIVE_INJECTIVE_DECIMALS)),
+ formatUnits(amount, NATIVE_INJECTIVE_DECIMALS),
+ symbol,
+ tokenName,
+ logo
+ )
+ )
+ );
+ }
+ })
+ .catch(() => {
+ if (!cancelled) {
+ // TODO: error state
+ }
+ });
+ } else {
+ const client = getInjectiveWasmClient();
+ client
+ .fetchSmartContractState(
+ targetAsset,
+ Buffer.from(
+ JSON.stringify({
+ token_info: {},
+ })
+ ).toString("base64")
+ )
+ .then((infoData) =>
+ client
+ .fetchSmartContractState(
+ targetAsset,
+ Buffer.from(
+ JSON.stringify({
+ balance: {
+ address: injAddress,
+ },
+ })
+ ).toString("base64")
+ )
+ .then((balanceData) => {
+ if (infoData && balanceData && !cancelled) {
+ const balance = parseSmartContractStateResponse(balanceData);
+ const info = parseSmartContractStateResponse(infoData);
+ dispatch(
+ setTargetParsedTokenAccount(
+ createParsedTokenAccount(
+ "",
+ "",
+ balance.balance.toString(),
+ info.decimals,
+ Number(formatUnits(balance.balance, info.decimals)),
+ formatUnits(balance.balance, info.decimals),
+ symbol,
+ tokenName,
+ logo
+ )
+ )
+ );
+ }
+ })
+ )
+ .catch((e) => {
+ if (!cancelled) {
+ // TODO: error state
+ }
+ });
+ }
+ }
return () => {
cancelled = true;
@@ -602,6 +693,7 @@ function useGetTargetParsedTokenAccounts() {
nearAccountId,
xplaWallet,
aptosAddress,
+ injAddress,
seiAddress,
suiAddress,
]);
diff --git a/src/hooks/useHandleAttest.tsx b/src/hooks/useHandleAttest.tsx
index 16b286925..563718040 100644
--- a/src/hooks/useHandleAttest.tsx
+++ b/src/hooks/useHandleAttest.tsx
@@ -2,12 +2,14 @@ import {
attestFromAlgorand,
attestFromAptos,
attestFromEth,
+ attestFromInjective,
attestFromSolana,
attestFromTerra,
attestFromXpla,
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_KLAYTN,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
@@ -15,6 +17,7 @@ import {
CHAIN_ID_SEI,
getEmitterAddressAlgorand,
getEmitterAddressEth,
+ getEmitterAddressInjective,
getEmitterAddressSolana,
getEmitterAddressTerra,
getEmitterAddressXpla,
@@ -23,6 +26,7 @@ import {
isTerraChain,
parseSequenceFromLogAlgorand,
parseSequenceFromLogEth,
+ parseSequenceFromLogInjective,
parseSequenceFromLogSolana,
parseSequenceFromLogTerra,
parseSequenceFromLogXpla,
@@ -91,9 +95,12 @@ import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana";
import { postWithFees, waitForTerraExecution } from "../utils/terra";
import { postWithFeesXpla, waitForXplaExecution } from "../utils/xpla";
+import { useInjectiveContext } from "../contexts/InjectiveWalletContext";
+import { broadcastInjectiveTx } from "../utils/injective";
import { AlgorandWallet } from "@xlabs-libs/wallet-aggregator-algorand";
import { SolanaWallet } from "@xlabs-libs/wallet-aggregator-solana";
import { AptosWallet } from "@xlabs-libs/wallet-aggregator-aptos";
+import { InjectiveWallet } from "@xlabs-libs/wallet-aggregator-injective";
import { NearWallet } from "@xlabs-libs/wallet-aggregator-near";
import { useTerraWallet } from "../contexts/TerraWalletContext";
import { TerraWallet } from "@xlabs-libs/wallet-aggregator-terra";
@@ -508,6 +515,63 @@ async function terra(
}
}
+async function injective(
+ dispatch: any,
+ enqueueSnackbar: any,
+ wallet: InjectiveWallet,
+ walletAddress: string,
+ asset: string,
+ onError: (error: any) => void,
+ onStart: (extra?: Partial) => void
+) {
+ dispatch(setIsSending(true));
+ try {
+ const tokenBridgeAddress =
+ getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE);
+ const msg = await attestFromInjective(
+ tokenBridgeAddress,
+ walletAddress,
+ asset
+ );
+ const tx = await broadcastInjectiveTx(
+ wallet,
+ walletAddress,
+ msg,
+ "Attest Token"
+ );
+ onStart?.({ txId: tx.txHash });
+ dispatch(setAttestTx({ id: tx.txHash, block: tx.height }));
+ enqueueSnackbar(null, {
+ content: Transaction confirmed,
+ });
+ const sequence = parseSequenceFromLogInjective(tx);
+ if (!sequence) {
+ throw new Error("Sequence not found");
+ }
+ const emitterAddress = await getEmitterAddressInjective(tokenBridgeAddress);
+ enqueueSnackbar(null, {
+ content: Fetching VAA,
+ });
+ const { vaaBytes } = await getSignedVAAWithRetry(
+ WORMHOLE_RPC_HOSTS,
+ CHAIN_ID_INJECTIVE,
+ emitterAddress,
+ sequence
+ );
+ dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
+ enqueueSnackbar(null, {
+ content: Fetched Signed VAA,
+ });
+ } catch (e) {
+ console.error(e);
+ enqueueSnackbar(null, {
+ content: {parseError(e)},
+ });
+ dispatch(setIsSending(false));
+ onError(e);
+ }
+}
+
async function sui(
dispatch: any,
enqueueSnackbar: any,
@@ -665,6 +729,7 @@ export function useHandleAttest() {
const { address: algoAccount, wallet: algoWallet } = useAlgorandWallet();
const { account: aptosAddress, wallet: aptosWallet } = useAptosContext();
const { accountId: nearAccountId, wallet } = useNearContext();
+ const { wallet: injWallet, address: injAddress } = useInjectiveContext();
const seiWallet = useSeiWallet();
const seiAddress = seiWallet?.getAddress();
const suiWallet = useSuiWallet();
@@ -757,6 +822,16 @@ export function useHandleAttest() {
onError,
onStart
);
+ } else if (sourceChain === CHAIN_ID_INJECTIVE && injWallet && injAddress) {
+ injective(
+ dispatch,
+ enqueueSnackbar,
+ injWallet,
+ injAddress,
+ sourceAsset,
+ onError,
+ onStart
+ );
} else if (sourceChain === CHAIN_ID_SEI && seiWallet && seiAddress) {
sei(dispatch, enqueueSnackbar, seiWallet, sourceAsset, onError, onStart);
} else if (
@@ -780,6 +855,8 @@ export function useHandleAttest() {
aptosAddress,
nearAccountId,
wallet,
+ injWallet,
+ injAddress,
seiWallet,
seiAddress,
suiWallet,
diff --git a/src/hooks/useHandleCreateWrapped.tsx b/src/hooks/useHandleCreateWrapped.tsx
index 56c96bb7a..c3ebdc12d 100644
--- a/src/hooks/useHandleCreateWrapped.tsx
+++ b/src/hooks/useHandleCreateWrapped.tsx
@@ -3,6 +3,7 @@ import {
CHAIN_ID_ACALA,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_KARURA,
CHAIN_ID_KLAYTN,
CHAIN_ID_NEAR,
@@ -11,6 +12,7 @@ import {
createWrappedOnAlgorand,
createWrappedOnAptos,
createWrappedOnEth,
+ createWrappedOnInjective,
createWrappedOnSolana,
createWrappedOnTerra,
createWrappedOnXpla,
@@ -19,6 +21,7 @@ import {
isTerraChain,
TerraChainId,
updateWrappedOnEth,
+ updateWrappedOnInjective,
updateWrappedOnSolana,
updateWrappedOnTerra,
updateWrappedOnXpla,
@@ -75,9 +78,12 @@ import parseError from "../utils/parseError";
import { postVaa, signSendAndConfirm } from "../utils/solana";
import { postWithFees } from "../utils/terra";
import useAttestSignedVAA from "./useAttestSignedVAA";
+import { broadcastInjectiveTx } from "../utils/injective";
+import { useInjectiveContext } from "../contexts/InjectiveWalletContext";
import { AlgorandWallet } from "@xlabs-libs/wallet-aggregator-algorand";
import { SolanaWallet } from "@xlabs-libs/wallet-aggregator-solana";
import { AptosWallet } from "@xlabs-libs/wallet-aggregator-aptos";
+import { InjectiveWallet } from "@xlabs-libs/wallet-aggregator-injective";
import { NearWallet } from "@xlabs-libs/wallet-aggregator-near";
import { useTerraWallet } from "../contexts/TerraWalletContext";
import { TerraWallet } from "@xlabs-libs/wallet-aggregator-terra";
@@ -543,6 +549,50 @@ async function terra(
}
}
+async function injective(
+ dispatch: any,
+ enqueueSnackbar: any,
+ wallet: InjectiveWallet,
+ walletAddress: string,
+ signedVAA: Uint8Array,
+ shouldUpdate: boolean,
+ onError: (error: any) => void,
+ onStart: (extra?: Partial) => void
+) {
+ dispatch(setIsCreating(true));
+ const tokenBridgeAddress = getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE);
+ try {
+ const msg = shouldUpdate
+ ? await updateWrappedOnInjective(
+ tokenBridgeAddress,
+ walletAddress,
+ signedVAA
+ )
+ : await createWrappedOnInjective(
+ tokenBridgeAddress,
+ walletAddress,
+ signedVAA
+ );
+ const tx = await broadcastInjectiveTx(
+ wallet,
+ walletAddress,
+ msg,
+ "Wormhole - Create Wrapped"
+ );
+ onStart({ txId: tx.txHash });
+ dispatch(setCreateTx({ id: tx.txHash, block: tx.height }));
+ enqueueSnackbar(null, {
+ content: Transaction confirmed,
+ });
+ } catch (e) {
+ enqueueSnackbar(null, {
+ content: {parseError(e)},
+ });
+ dispatch(setIsCreating(false));
+ onError(e);
+ }
+}
+
async function sui(
dispatch: any,
enqueueSnackbar: any,
@@ -598,7 +648,7 @@ async function sui(
}
const wrappedAssetSetupEvent =
suiPrepareRegistrationTxRes.objectChanges?.find(
- (oc: any) =>
+ (oc) =>
oc.type === "created" && oc.objectType.includes("WrappedAssetSetup")
);
const wrappedAssetSetupType =
@@ -757,6 +807,7 @@ export function useHandleCreateWrapped(
const xplaWallet = useXplaWallet();
const { address: algoAccount, wallet: algoWallet } = useAlgorandWallet();
const { account: aptosAddress, wallet: aptosWallet } = useAptosContext();
+ const { wallet: injWallet, address: injAddress } = useInjectiveContext();
const { accountId: nearAccountId, wallet } = useNearContext();
const suiWallet = useSuiWallet();
const seiWallet = useSeiWallet();
@@ -886,6 +937,22 @@ export function useHandleCreateWrapped(
onError,
onStart
);
+ } else if (
+ targetChain === CHAIN_ID_INJECTIVE &&
+ injWallet &&
+ injAddress &&
+ !!signedVAA
+ ) {
+ injective(
+ dispatch,
+ enqueueSnackbar,
+ injWallet,
+ injAddress,
+ signedVAA,
+ shouldUpdate,
+ onError,
+ onStart
+ );
} else if (
targetChain === CHAIN_ID_SUI &&
suiWallet &&
@@ -939,6 +1006,8 @@ export function useHandleCreateWrapped(
xplaWallet,
aptosAddress,
aptosWallet,
+ injWallet,
+ injAddress,
foreignAddress,
suiWallet,
seiWallet,
diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx
index 2b4402a42..e06d70a91 100644
--- a/src/hooks/useHandleRedeem.tsx
+++ b/src/hooks/useHandleRedeem.tsx
@@ -2,6 +2,7 @@ import {
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_KLAYTN,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
@@ -11,6 +12,7 @@ import {
redeemOnAlgorand,
redeemOnEth,
redeemOnEthNative,
+ redeemOnInjective,
redeemOnSolana,
redeemOnTerra,
redeemOnXpla,
@@ -79,9 +81,12 @@ import { postVaa, signSendAndConfirm } from "../utils/solana";
import { postWithFees } from "../utils/terra";
import useTransferSignedVAA from "./useTransferSignedVAA";
import { postWithFeesXpla } from "../utils/xpla";
+import { broadcastInjectiveTx } from "../utils/injective";
+import { useInjectiveContext } from "../contexts/InjectiveWalletContext";
import { AlgorandWallet } from "@xlabs-libs/wallet-aggregator-algorand";
import { SolanaWallet } from "@xlabs-libs/wallet-aggregator-solana";
import { AptosWallet } from "@xlabs-libs/wallet-aggregator-aptos";
+import { InjectiveWallet } from "@xlabs-libs/wallet-aggregator-injective";
import { NearWallet } from "@xlabs-libs/wallet-aggregator-near";
import { useTerraWallet } from "../contexts/TerraWalletContext";
import { TerraWallet } from "@xlabs-libs/wallet-aggregator-terra";
@@ -335,7 +340,7 @@ async function sei(
try {
const vaa = parseVaa(signedVAA);
const transfer = parseTokenTransferPayload(vaa.payload);
- const receiver = cosmos.humanAddress("sei", new Uint8Array(transfer.to));
+ const receiver = cosmos.humanAddress("sei", transfer.to);
const contractAddress =
receiver === SEI_TRANSLATOR
? SEI_TRANSLATOR
@@ -397,6 +402,40 @@ async function sei(
}
}
+async function injective(
+ dispatch: any,
+ enqueueSnackbar: any,
+ wallet: InjectiveWallet,
+ walletAddress: string,
+ signedVAA: Uint8Array,
+ onSuccess?: () => void
+) {
+ dispatch(setIsRedeeming(true));
+ try {
+ const msg = await redeemOnInjective(
+ getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE),
+ walletAddress,
+ signedVAA
+ );
+ const tx = await broadcastInjectiveTx(
+ wallet,
+ walletAddress,
+ msg,
+ "Wormhole - Complete Transfer"
+ );
+ dispatch(setRedeemTx({ id: tx.txHash, block: tx.height }));
+ enqueueSnackbar(null, {
+ content: Transaction confirmed,
+ });
+ onSuccess?.();
+ } catch (e) {
+ enqueueSnackbar(null, {
+ content: {parseError(e)},
+ });
+ dispatch(setIsRedeeming(false));
+ }
+}
+
async function solana(
dispatch: any,
enqueueSnackbar: any,
@@ -566,6 +605,7 @@ export function useHandleRedeem() {
const { address: algoAccount, wallet: algoWallet } = useAlgorandWallet();
const { accountId: nearAccountId, wallet } = useNearContext();
const { account: aptosAddress, wallet: aptosWallet } = useAptosContext();
+ const { wallet: injWallet, address: injAddress } = useInjectiveContext();
const suiWallet = useSuiWallet();
const seiWallet = useSeiWallet();
const seiAddress = seiWallet?.getAddress();
@@ -674,6 +714,20 @@ export function useHandleRedeem() {
wallet,
onSuccess
);
+ } else if (
+ targetChain === CHAIN_ID_INJECTIVE &&
+ injWallet &&
+ injAddress &&
+ signedVAA
+ ) {
+ injective(
+ dispatch,
+ enqueueSnackbar,
+ injWallet,
+ injAddress,
+ signedVAA,
+ onSuccess
+ );
} else if (
targetChain === CHAIN_ID_SUI &&
suiWallet?.getAddress() &&
@@ -695,6 +749,8 @@ export function useHandleRedeem() {
algoAccount,
nearAccountId,
wallet,
+ injWallet,
+ injAddress,
suiWallet,
dispatch,
enqueueSnackbar,
@@ -749,6 +805,20 @@ export function useHandleRedeem() {
!!signedVAA
) {
algo(dispatch, enqueueSnackbar, algoWallet, signedVAA, onSuccess);
+ } else if (
+ targetChain === CHAIN_ID_INJECTIVE &&
+ injWallet &&
+ injAddress &&
+ signedVAA
+ ) {
+ injective(
+ dispatch,
+ enqueueSnackbar,
+ injWallet,
+ injAddress,
+ signedVAA,
+ onSuccess
+ );
} else if (
targetChain === CHAIN_ID_SEI &&
seiWallet &&
@@ -771,6 +841,8 @@ export function useHandleRedeem() {
solPK,
terraWallet,
algoAccount,
+ injWallet,
+ injAddress,
seiWallet,
seiAddress,
suiWallet,
diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx
index 1dec01294..8ea67c908 100644
--- a/src/hooks/useHandleTransfer.tsx
+++ b/src/hooks/useHandleTransfer.tsx
@@ -2,6 +2,7 @@ import {
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
+ CHAIN_ID_INJECTIVE,
CHAIN_ID_KLAYTN,
CHAIN_ID_SOLANA,
CHAIN_ID_XPLA,
@@ -10,6 +11,7 @@ import {
createNonce,
getEmitterAddressAlgorand,
getEmitterAddressEth,
+ getEmitterAddressInjective,
getEmitterAddressSolana,
getEmitterAddressTerra,
getEmitterAddressXpla,
@@ -18,6 +20,7 @@ import {
isTerraChain,
parseSequenceFromLogAlgorand,
parseSequenceFromLogEth,
+ parseSequenceFromLogInjective,
parseSequenceFromLogSolana,
parseSequenceFromLogTerra,
parseSequenceFromLogXpla,
@@ -25,6 +28,7 @@ import {
transferFromAlgorand,
transferFromEth,
transferFromEthNative,
+ transferFromInjective,
transferFromTerra,
transferFromXpla,
transferFromAptos,
@@ -117,9 +121,12 @@ import { signSendAndConfirm } from "../utils/solana";
import { postWithFees, waitForTerraExecution } from "../utils/terra";
import useTransferTargetAddressHex from "./useTransferTargetAddress";
import { postWithFeesXpla, waitForXplaExecution } from "../utils/xpla";
+import { broadcastInjectiveTx } from "../utils/injective";
+import { useInjectiveContext } from "../contexts/InjectiveWalletContext";
import { AlgorandWallet } from "@xlabs-libs/wallet-aggregator-algorand";
import { SolanaWallet } from "@xlabs-libs/wallet-aggregator-solana";
import { AptosWallet } from "@xlabs-libs/wallet-aggregator-aptos";
+import { InjectiveWallet } from "@xlabs-libs/wallet-aggregator-injective";
import { NearWallet } from "@xlabs-libs/wallet-aggregator-near";
import { useTerraWallet } from "../contexts/TerraWalletContext";
import { TerraWallet } from "@xlabs-libs/wallet-aggregator-terra";
@@ -796,6 +803,68 @@ async function terra(
}
}
+async function injective(
+ dispatch: any,
+ enqueueSnackbar: any,
+ wallet: InjectiveWallet,
+ walletAddress: string,
+ asset: string,
+ amount: string,
+ decimals: number,
+ targetChain: ChainId,
+ targetAddress: Uint8Array,
+ maybeAdditionalPayload: MaybeAdditionalPayloadFn,
+ relayerFee?: string,
+ onError?: (error: any) => void,
+ onStart?: (extra?: Partial) => void
+) {
+ dispatch(setIsSending(true));
+ try {
+ const baseAmountParsed = parseUnits(amount, decimals);
+ const feeParsed = parseUnits(relayerFee || "0", decimals);
+ const transferAmountParsed = baseAmountParsed.add(feeParsed);
+ const additionalPayload = maybeAdditionalPayload();
+ const tokenBridgeAddress =
+ getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE);
+ const msgs = await transferFromInjective(
+ walletAddress,
+ tokenBridgeAddress,
+ asset,
+ transferAmountParsed.toString(),
+ targetChain,
+ additionalPayload?.receivingContract || targetAddress,
+ feeParsed.toString(),
+ additionalPayload?.payload
+ );
+ const tx = await broadcastInjectiveTx(
+ wallet,
+ walletAddress,
+ msgs,
+ "Wormhole - Initiate Transfer"
+ );
+ onStart?.({ txId: tx.txHash });
+ dispatch(setTransferTx({ id: tx.txHash, block: tx.height }));
+ enqueueSnackbar(null, {
+ content: Transaction confirmed,
+ });
+ const sequence = parseSequenceFromLogInjective(tx);
+ if (!sequence) {
+ throw new Error("Sequence not found");
+ }
+ const emitterAddress = await getEmitterAddressInjective(tokenBridgeAddress);
+ await fetchSignedVAA(
+ CHAIN_ID_INJECTIVE,
+ emitterAddress,
+ sequence,
+ enqueueSnackbar,
+ dispatch
+ );
+ } catch (e) {
+ handleError(e, enqueueSnackbar, dispatch);
+ onError?.(e);
+ }
+}
+
async function sei(
dispatch: any,
enqueueSnackbar: any,
@@ -1022,6 +1091,7 @@ export function useHandleTransfer() {
const { address: algoAccount, wallet: algoWallet } = useAlgorandWallet();
const { accountId: nearAccountId, wallet } = useNearContext();
const { account: aptosAddress, wallet: aptosWallet } = useAptosContext();
+ const { wallet: injWallet, address: injAddress } = useInjectiveContext();
const suiWallet = useSuiWallet();
const seiWallet = useSeiWallet();
const seiAddress = seiWallet?.getAddress();
@@ -1294,6 +1364,29 @@ export function useHandleTransfer() {
onError,
onStart
);
+ } else if (
+ sourceChain === CHAIN_ID_INJECTIVE &&
+ injWallet &&
+ injAddress &&
+ !!sourceAsset &&
+ decimals !== undefined &&
+ !!targetAddress
+ ) {
+ injective(
+ dispatch,
+ enqueueSnackbar,
+ injWallet,
+ injAddress,
+ sourceAsset,
+ amount,
+ decimals,
+ targetChain,
+ targetAddress,
+ maybeAdditionalPayload,
+ relayerFee,
+ onError,
+ onStart
+ );
} else if (
sourceChain === CHAIN_ID_SUI &&
suiWallet?.isConnected() &&
@@ -1341,6 +1434,8 @@ export function useHandleTransfer() {
nearAccountId,
wallet,
aptosAddress,
+ injWallet,
+ injAddress,
suiWallet,
dispatch,
enqueueSnackbar,
diff --git a/src/hooks/useInjectiveMetadata.ts b/src/hooks/useInjectiveMetadata.ts
new file mode 100644
index 000000000..940302b3f
--- /dev/null
+++ b/src/hooks/useInjectiveMetadata.ts
@@ -0,0 +1,88 @@
+import { parseSmartContractStateResponse } from "@certusone/wormhole-sdk";
+import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts";
+import { useLayoutEffect, useMemo, useState } from "react";
+import { DataWrapper } from "../store/helpers";
+import { getInjectiveWasmClient } from "../utils/injective";
+
+export type InjectiveMetadata = {
+ symbol?: string;
+ logo?: string;
+ tokenName?: string;
+ decimals?: number;
+};
+
+const fetchSingleMetadata = async (address: string, client: ChainGrpcWasmApi) =>
+ client
+ .fetchSmartContractState(
+ address,
+ Buffer.from(JSON.stringify({ token_info: {} })).toString("base64")
+ )
+ .then((data) => {
+ const parsed = parseSmartContractStateResponse(data);
+ return {
+ symbol: parsed.symbol,
+ tokenName: parsed.name,
+ decimals: parsed.decimals,
+ } as InjectiveMetadata;
+ });
+
+const fetchInjectiveMetadata = async (addresses: string[]) => {
+ const client = getInjectiveWasmClient();
+ const promises: Promise[] = [];
+ addresses.forEach((address) => {
+ promises.push(fetchSingleMetadata(address, client));
+ });
+ const resultsArray = await Promise.all(promises);
+ const output = new Map();
+ addresses.forEach((address, index) => {
+ output.set(address, resultsArray[index]);
+ });
+
+ return output;
+};
+
+const useInjectiveMetadata = (
+ addresses: string[]
+): DataWrapper