From 0880d8a3ef4d2747a60b09c099ecbb96ba7e38b1 Mon Sep 17 00:00:00 2001 From: Evan Gray Date: Tue, 20 Jun 2023 21:21:14 -0400 Subject: [PATCH] handle native sei transfers --- src/components/Recovery.tsx | 60 +++++++++++++ src/hooks/useHandleAttest.tsx | 76 +++++++++++++++++ src/hooks/useHandleRedeem.tsx | 60 +++++++++---- src/hooks/useHandleTransfer.tsx | 136 ++++++++++++++++++++++-------- src/hooks/useSeiNativeBalances.ts | 48 +++++------ src/utils/sei.ts | 62 +++++++++++++- 6 files changed, 365 insertions(+), 77 deletions(-) diff --git a/src/components/Recovery.tsx b/src/components/Recovery.tsx index 5ea4124..bac0e25 100644 --- a/src/components/Recovery.tsx +++ b/src/components/Recovery.tsx @@ -5,6 +5,7 @@ import { CHAIN_ID_INJECTIVE, CHAIN_ID_KARURA, CHAIN_ID_NEAR, + CHAIN_ID_SEI, CHAIN_ID_SOLANA, CHAIN_ID_SUI, CHAIN_ID_TERRA2, @@ -105,6 +106,12 @@ import { } from "../utils/injective"; import { makeNearProvider } from "../utils/near"; import parseError from "../utils/parseError"; +import { + getSeiQueryClient, + getSeiWasmClient, + parseSequenceFromLogSei, + queryExternalIdSei, +} from "../utils/sei"; import { getSuiProvider } from "../utils/sui"; import ButtonWithLoader from "./ButtonWithLoader"; import ChainSelect from "./ChainSelect"; @@ -320,6 +327,26 @@ async function injective(txHash: string, enqueueSnackbar: any) { } } +async function sei(txHash: string, enqueueSnackbar: any) { + try { + const client = await getSeiQueryClient(); + const tx = await client.cosmos.tx.v1beta1.getTx({ hash: txHash }); + if (!tx || !tx.tx_response) { + throw new Error("Unable to fetch transaction"); + } + const sequence = parseSequenceFromLogSei(tx.tx_response); + if (!sequence) { + throw new Error("Sequence not found"); + } + const emitterAddress = await getEmitterAddressTerra( + getTokenBridgeAddressForChain(CHAIN_ID_SEI) + ); + return await fetchSignedVAA(CHAIN_ID_SEI, emitterAddress, sequence); + } catch (e) { + return handleError(e, enqueueSnackbar); + } +} + async function sui(digest: string, enqueueSnackbar: any) { try { const provider = getSuiProvider(); @@ -568,6 +595,19 @@ export default function Recovery() { } })(); } + if (parsedPayload && parsedPayload.targetChain === CHAIN_ID_SEI) { + (async () => { + const client = await getSeiWasmClient(); + const tokenId = await queryExternalIdSei( + client, + getTokenBridgeAddressForChain(CHAIN_ID_SEI), + parsedPayload.originAddress + ); + if (!cancelled) { + setTokenId(tokenId || ""); + } + })(); + } if (parsedPayload && parsedPayload.targetChain === CHAIN_ID_SUI) { (async () => { const tokenId = await getForeignAssetSui( @@ -777,6 +817,26 @@ export default function Recovery() { setIsVAAPending(isPending); } })(); + } else if (recoverySourceChain === CHAIN_ID_SEI) { + setRecoverySourceTxError(""); + setRecoverySourceTxIsLoading(true); + setTokenId(""); + (async () => { + const { vaa, isPending, error } = await sei( + 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); diff --git a/src/hooks/useHandleAttest.tsx b/src/hooks/useHandleAttest.tsx index 12d06d9..ffa3120 100644 --- a/src/hooks/useHandleAttest.tsx +++ b/src/hooks/useHandleAttest.tsx @@ -4,6 +4,7 @@ import { CHAIN_ID_INJECTIVE, CHAIN_ID_KLAYTN, CHAIN_ID_NEAR, + CHAIN_ID_SEI, CHAIN_ID_SOLANA, CHAIN_ID_SUI, CHAIN_ID_XPLA, @@ -40,9 +41,15 @@ import { } from "@certusone/wormhole-sdk"; import { getOriginalPackageId } from "@certusone/wormhole-sdk/lib/cjs/sui"; import { getEmitterAddressAndSequenceFromResponseSui } from "@certusone/wormhole-sdk/lib/esm/sui"; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { calculateFee } from "@cosmjs/stargate"; import { WalletStrategy } from "@injectivelabs/wallet-ts"; import { Alert } from "@material-ui/lab"; import { Wallet } from "@near-wallet-selector/core"; +import { + useSigningCosmWasmClient as useSeiSigningCosmWasmClient, + useWallet as useSeiWallet, +} from "@sei-js/react"; import { WalletContextState } from "@solana/wallet-adapter-react"; import { Connection, PublicKey } from "@solana/web3.js"; import { @@ -113,6 +120,7 @@ import { signSendAndConfirm } from "../utils/solana"; import { getSuiProvider } from "../utils/sui"; import { postWithFees, waitForTerraExecution } from "../utils/terra"; import { postWithFeesXpla, waitForXplaExecution } from "../utils/xpla"; +import { attestFromSeiMsg, parseSequenceFromLogSei } from "../utils/sei"; async function algo( dispatch: any, @@ -543,6 +551,56 @@ async function injective( } } +async function sei( + dispatch: any, + enqueueSnackbar: any, + wallet: SigningCosmWasmClient, + walletAddress: string, + asset: string +) { + dispatch(setIsSending(true)); + try { + const msg = attestFromSeiMsg(asset); + const fee = calculateFee(600000, "0.1usei"); + const tokenBridgeAddress = getTokenBridgeAddressForChain(CHAIN_ID_SEI); + const tx = await wallet.execute( + walletAddress, + tokenBridgeAddress, + msg, + fee, + "Wormhole - Create Wrapped" + ); + dispatch(setAttestTx({ id: tx.transactionHash, block: tx.height })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + const sequence = parseSequenceFromLogSei(tx); + if (!sequence) { + throw new Error("Sequence not found"); + } + const emitterAddress = await getEmitterAddressTerra(tokenBridgeAddress); + enqueueSnackbar(null, { + content: Fetching VAA, + }); + const { vaaBytes } = await getSignedVAAWithRetry( + WORMHOLE_RPC_HOSTS, + CHAIN_ID_SEI, + 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)); + } +} + async function sui( dispatch: any, enqueueSnackbar: any, @@ -624,6 +682,10 @@ export function useHandleAttest() { const { account: aptosAccount, signAndSubmitTransaction } = useAptosContext(); const aptosAddress = aptosAccount?.address?.toString(); const { wallet: injWallet, address: injAddress } = useInjectiveContext(); + const { signingCosmWasmClient: seiSigningCosmWasmClient } = + useSeiSigningCosmWasmClient(); + const { accounts: seiAccounts } = useSeiWallet(); + const seiAddress = seiAccounts.length ? seiAccounts[0].address : null; const { accountId: nearAccountId, wallet: nearWallet } = useNearContext(); const suiWallet = useWallet(); const disabled = !isTargetComplete || isSending || isSendComplete; @@ -649,6 +711,18 @@ export function useHandleAttest() { aptos(dispatch, enqueueSnackbar, sourceAsset, signAndSubmitTransaction); } else if (sourceChain === CHAIN_ID_INJECTIVE && injWallet && injAddress) { injective(dispatch, enqueueSnackbar, injWallet, injAddress, sourceAsset); + } else if ( + sourceChain === CHAIN_ID_SEI && + seiSigningCosmWasmClient && + seiAddress + ) { + sei( + dispatch, + enqueueSnackbar, + seiSigningCosmWasmClient, + seiAddress, + sourceAsset + ); } else if (sourceChain === CHAIN_ID_NEAR && nearAccountId && nearWallet) { near(dispatch, enqueueSnackbar, nearAccountId, sourceAsset, nearWallet); } else if ( @@ -677,6 +751,8 @@ export function useHandleAttest() { nearAccountId, nearWallet, suiWallet, + seiSigningCosmWasmClient, + seiAddress, ]); return useMemo( () => ({ diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index 8300716..7dc5553 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -12,6 +12,8 @@ import { TerraChainId, isEVMChain, isTerraChain, + parseTransferPayload, + parseVaa, postVaaSolanaWithRetry, redeemAndUnwrapOnSolana, redeemOnAlgorand, @@ -81,6 +83,7 @@ import { ALGORAND_TOKEN_BRIDGE_ID, MAX_VAA_UPLOAD_RETRIES_SOLANA, NEAR_TOKEN_BRIDGE_ACCOUNT, + SEI_TRANSLATER_TARGET, SEI_TRANSLATOR, SOLANA_HOST, SOL_BRIDGE_ADDRESS, @@ -412,24 +415,45 @@ async function sei( ) { dispatch(setIsRedeeming(true)); try { - const msg = { - complete_transfer_and_convert: { - vaa: fromUint8Array(signedVAA), - }, - }; - // TODO: is this right? - const fee = calculateFee(800000, "0.1usei"); - const tx = await wallet.execute( - walletAddress, - SEI_TRANSLATOR, - msg, - fee, - "Wormhole - Complete Transfer" - ); - dispatch(setRedeemTx({ id: tx.transactionHash, block: tx.height })); - enqueueSnackbar(null, { - content: Transaction confirmed, - }); + const parsed = parseVaa(signedVAA); + const payload = parseTransferPayload(parsed.payload); + if (payload.targetAddress === uint8ArrayToHex(SEI_TRANSLATER_TARGET)) { + const msg = { + complete_transfer_and_convert: { + vaa: fromUint8Array(signedVAA), + }, + }; + const fee = calculateFee(800000, "0.1usei"); + const tx = await wallet.execute( + walletAddress, + SEI_TRANSLATOR, + msg, + fee, + "Wormhole - Complete Transfer" + ); + dispatch(setRedeemTx({ id: tx.transactionHash, block: tx.height })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + } else { + const msg = { + submit_vaa: { + data: fromUint8Array(signedVAA), + }, + }; + const fee = calculateFee(800000, "0.1usei"); + const tx = await wallet.execute( + walletAddress, + getTokenBridgeAddressForChain(CHAIN_ID_SEI), + msg, + fee, + "Wormhole - Complete Transfer" + ); + dispatch(setRedeemTx({ id: tx.transactionHash, block: tx.height })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + } } catch (e) { enqueueSnackbar(null, { content: {parseError(e)}, diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 06d39bb..4536f92 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -130,7 +130,7 @@ import { signAndSendTransactions, } from "../utils/near"; import parseError from "../utils/parseError"; -import { parseSequenceFromLogSei } from "../utils/sei"; +import { isNativeDenomSei, parseSequenceFromLogSei } from "../utils/sei"; import { signSendAndConfirm } from "../utils/solana"; import { getSuiProvider } from "../utils/sui"; import { postWithFees, waitForTerraExecution } from "../utils/terra"; @@ -144,9 +144,10 @@ type AdditionalPayloadOverride = { function maybeAdditionalPayload( recipientChain: ChainId, - recipientAddress: Uint8Array + recipientAddress: Uint8Array, + originChain?: ChainId ): AdditionalPayloadOverride | null { - if (recipientChain === CHAIN_ID_SEI) { + if (recipientChain === CHAIN_ID_SEI && originChain !== CHAIN_ID_SEI) { return { receivingContract: SEI_TRANSLATER_TARGET, payload: new Uint8Array( @@ -216,6 +217,7 @@ async function algo( recipientChain: ChainId, recipientAddress: Uint8Array, chainId: ChainId, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -225,7 +227,8 @@ async function algo( const transferAmountParsed = baseAmountParsed.add(feeParsed); const additionalPayload = maybeAdditionalPayload( recipientChain, - recipientAddress + recipientAddress, + originChain ); const algodClient = new algosdk.Algodv2( ALGORAND_HOST.algodToken, @@ -283,6 +286,7 @@ async function aptos( ) => Promise<{ hash: string; }>, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -293,7 +297,8 @@ async function aptos( const transferAmountParsed = baseAmountParsed.add(feeParsed); const additionalPayload = maybeAdditionalPayload( recipientChain, - recipientAddress + recipientAddress, + originChain ); if (additionalPayload?.payload) { throw new Error("Transfer with payload is unsupported on Aptos"); @@ -346,6 +351,7 @@ async function evm( recipientAddress: Uint8Array, isNative: boolean, chainId: ChainId, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -355,7 +361,8 @@ async function evm( const transferAmountParsed = baseAmountParsed.add(feeParsed); const additionalPayload = maybeAdditionalPayload( recipientChain, - recipientAddress + recipientAddress, + originChain ); // Klaytn requires specifying gasPrice const overrides = @@ -420,6 +427,7 @@ async function near( recipientChain: ChainId, recipientAddress: Uint8Array, chainId: ChainId, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -429,7 +437,8 @@ async function near( const transferAmountParsed = baseAmountParsed.add(feeParsed); const additionalPayload = maybeAdditionalPayload( recipientChain, - recipientAddress + recipientAddress, + originChain ); const account = await makeNearAccount(senderAddr); const msgs = @@ -513,7 +522,8 @@ async function solana( const transferAmountParsed = baseAmountParsed.add(feeParsed); const additionalPayload = maybeAdditionalPayload( targetChain, - targetAddress + targetAddress, + originChain ); const originAddress = originAddressStr ? zeroPad(hexToUint8Array(originAddressStr), 32) @@ -583,6 +593,7 @@ async function terra( targetAddress: Uint8Array, feeDenom: string, chainId: TerraChainId, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -593,7 +604,8 @@ async function terra( const tokenBridgeAddress = getTokenBridgeAddressForChain(chainId); const additionalPayload = maybeAdditionalPayload( targetChain, - targetAddress + targetAddress, + originChain ); const msgs = await transferFromTerra( wallet.terraAddress, @@ -645,6 +657,7 @@ async function xpla( decimals: number, targetChain: ChainId, targetAddress: Uint8Array, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -655,7 +668,8 @@ async function xpla( const tokenBridgeAddress = getTokenBridgeAddressForChain(CHAIN_ID_XPLA); const additionalPayload = maybeAdditionalPayload( targetChain, - targetAddress + targetAddress, + originChain ); const msgs = await transferFromXpla( wallet.xplaAddress, @@ -706,6 +720,7 @@ async function injective( decimals: number, targetChain: ChainId, targetAddress: Uint8Array, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -717,7 +732,8 @@ async function injective( getTokenBridgeAddressForChain(CHAIN_ID_INJECTIVE); const additionalPayload = maybeAdditionalPayload( targetChain, - targetAddress + targetAddress, + originChain ); const msgs = await transferFromInjective( walletAddress, @@ -774,28 +790,73 @@ async function sei( const feeParsed = parseUnits(relayerFee || "0", decimals); const transferAmountParsed = baseAmountParsed.add(feeParsed); const tokenBridgeAddress = getTokenBridgeAddressForChain(CHAIN_ID_SEI); - // NOTE: this only supports transferring out via the Sei CW20 <> Bank translator - const msg = { - convert_and_transfer: { - recipient_chain: targetChain, - recipient: Buffer.from(targetAddress).toString("base64"), - fee: feeParsed.toString(), - }, - }; - const fee = calculateFee(600000, "0.1usei"); - const tx = await wallet.execute( - walletAddress, - SEI_TRANSLATOR, - msg, - fee, - "Wormhole - Initiate Transfer", - [{ denom: asset, amount: transferAmountParsed.toString() }] - ); - dispatch(setTransferTx({ id: tx.transactionHash, block: tx.height })); - enqueueSnackbar(null, { - content: Transaction confirmed, - }); - const sequence = parseSequenceFromLogSei(tx); + let sequence: string = ""; + if (asset.startsWith(`factory/${SEI_TRANSLATOR}/`)) { + const msg = { + convert_and_transfer: { + recipient_chain: targetChain, + recipient: Buffer.from(targetAddress).toString("base64"), + fee: feeParsed.toString(), + }, + }; + const fee = calculateFee(750000, "0.1usei"); + const tx = await wallet.execute( + walletAddress, + SEI_TRANSLATOR, + msg, + fee, + "Wormhole - Initiate Transfer", + [{ denom: asset, amount: transferAmountParsed.toString() }] + ); + dispatch(setTransferTx({ id: tx.transactionHash, block: tx.height })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + sequence = parseSequenceFromLogSei(tx); + } else if (isNativeDenomSei(asset)) { + const fee = calculateFee(600000, "0.1usei"); + const nonce = Math.round(Math.random() * 100000); + const tx = await wallet.executeMultiple( + walletAddress, + [ + { + contractAddress: tokenBridgeAddress, + msg: { + deposit_tokens: {}, + }, + funds: [{ denom: asset, amount: transferAmountParsed.toString() }], + }, + { + contractAddress: tokenBridgeAddress, + msg: { + initiate_transfer: { + asset: { + amount: transferAmountParsed.toString(), + info: { + native_token: { + denom: asset, + }, + }, + }, + recipient_chain: targetChain, + recipient: Buffer.from(targetAddress).toString("base64"), + fee: feeParsed.toString(), + nonce, + }, + }, + }, + ], + fee, + "Wormhole - Initiate Transfer" + ); + dispatch(setTransferTx({ id: tx.transactionHash, block: tx.height })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + sequence = parseSequenceFromLogSei(tx); + } else { + throw new Error("Unsupported asset"); + } if (!sequence) { throw new Error("Sequence not found"); } @@ -822,6 +883,7 @@ async function sui( decimals: number, targetChain: ChainId, targetAddress: Uint8Array, + originChain?: ChainId, relayerFee?: string ) { dispatch(setIsSending(true)); @@ -947,6 +1009,7 @@ export function useHandleTransfer() { targetAddress, isNative, sourceChain, + originChain, relayerFee ); } else if ( @@ -992,6 +1055,7 @@ export function useHandleTransfer() { targetAddress, terraFeeDenom, sourceChain, + originChain, relayerFee ); } else if ( @@ -1010,6 +1074,7 @@ export function useHandleTransfer() { decimals, targetChain, targetAddress, + originChain, relayerFee ); } else if ( @@ -1029,6 +1094,7 @@ export function useHandleTransfer() { targetChain, targetAddress, sourceChain, + originChain, relayerFee ); } else if ( @@ -1048,6 +1114,7 @@ export function useHandleTransfer() { targetAddress, sourceChain, signAndSubmitTransaction, + originChain, relayerFee ); } else if ( @@ -1068,6 +1135,7 @@ export function useHandleTransfer() { decimals, targetChain, targetAddress, + originChain, relayerFee ); } else if ( @@ -1109,6 +1177,7 @@ export function useHandleTransfer() { targetChain, targetAddress, sourceChain, + originChain, relayerFee ); } else if ( @@ -1128,6 +1197,7 @@ export function useHandleTransfer() { decimals, targetChain, targetAddress, + originChain, relayerFee ); } diff --git a/src/hooks/useSeiNativeBalances.ts b/src/hooks/useSeiNativeBalances.ts index fb1c9fe..b2fee4e 100644 --- a/src/hooks/useSeiNativeBalances.ts +++ b/src/hooks/useSeiNativeBalances.ts @@ -2,7 +2,7 @@ import { cosmos } from "@certusone/wormhole-sdk"; import { base58, formatUnits } from "ethers/lib/utils"; import { MutableRefObject, useEffect, useMemo, useState } from "react"; import { NFTParsedTokenAccount } from "../store/nftSlice"; -import { SEI_TRANSLATOR } from "../utils/consts"; +import { SEI_DECIMALS, SEI_TRANSLATOR } from "../utils/consts"; import { getSeiQueryClient, getSeiWasmClient } from "../utils/sei"; export default function useSeiNativeBalances( @@ -35,9 +35,9 @@ export default function useSeiNativeBalances( address: walletAddress, }); // NOTE: this UI only handles the translator factory tokens for now - // const seiCoin = response.balances.find( - // (coin) => coin.denom === "usei" - // ); + const seiCoin = response.balances.find( + (coin) => coin.denom === "usei" + ); const translatedCoins = response.balances.filter((coin) => coin.denom.startsWith(`factory/${SEI_TRANSLATOR}/`) ); @@ -59,26 +59,26 @@ export default function useSeiNativeBalances( ) ); const tokenAccounts: NFTParsedTokenAccount[] = [ - // ...(seiCoin - // ? [ - // { - // amount: seiCoin.amount, - // decimals: SEI_DECIMALS, - // mintKey: seiCoin.denom, - // publicKey: walletAddress, - // uiAmount: Number( - // formatUnits(BigInt(seiCoin.amount), SEI_DECIMALS) - // ), - // uiAmountString: formatUnits( - // BigInt(seiCoin.amount), - // SEI_DECIMALS - // ), - // isNativeAsset: true, - // symbol: "SEI", - // name: "Sei", - // }, - // ] - // : []), + ...(seiCoin + ? [ + { + amount: seiCoin.amount, + decimals: SEI_DECIMALS, + mintKey: seiCoin.denom, + publicKey: walletAddress, + uiAmount: Number( + formatUnits(BigInt(seiCoin.amount), SEI_DECIMALS) + ), + uiAmountString: formatUnits( + BigInt(seiCoin.amount), + SEI_DECIMALS + ), + isNativeAsset: true, + symbol: "SEI", + name: "Sei", + }, + ] + : []), ...translatedCoins.map((coin, idx) => ({ amount: coin.amount, decimals: translatedCoinInfos[idx].decimals, diff --git a/src/utils/sei.ts b/src/utils/sei.ts index f10f332..e7d8b85 100644 --- a/src/utils/sei.ts +++ b/src/utils/sei.ts @@ -1,14 +1,21 @@ import { + CHAIN_ID_INJECTIVE, CHAIN_ID_SEI, + CHAIN_ID_TERRA, + CHAIN_ID_XPLA, ChainId, ChainName, + CosmWasmChainId, + CosmWasmChainName, WormholeWrappedInfo, - buildTokenId, coalesceChainId, + coalesceCosmWasmChainId, hexToUint8Array, - isNativeCosmWasmDenom, + isTerraChain, } from "@certusone/wormhole-sdk"; +import { isNativeDenom } from "@certusone/wormhole-sdk/lib/esm/terra"; import { getCosmWasmClient, getQueryClient } from "@sei-js/core"; +import { keccak256 } from "ethers/lib/utils"; import { fromUint8Array } from "js-base64"; import { SEI_CHAIN_CONFIGURATION } from "./consts"; @@ -22,6 +29,57 @@ export type CosmWasmClient = { queryContractSmart: (address: string, queryMsg: any) => Promise; }; +// START SDK PATCH +export const isNativeDenomInjective = (denom: string) => denom === "inj"; +export const isNativeDenomXpla = (denom: string) => denom === "axpla"; +export const isNativeDenomSei = (denom: string) => denom === "usei"; + +export function isNativeCosmWasmDenom( + chainId: CosmWasmChainId, + address: string +) { + return ( + (isTerraChain(chainId) && isNativeDenom(address)) || + (chainId === CHAIN_ID_INJECTIVE && isNativeDenomInjective(address)) || + (chainId === CHAIN_ID_XPLA && isNativeDenomXpla(address)) || + (chainId === CHAIN_ID_SEI && isNativeDenomSei(address)) + ); +} + +export function buildTokenId( + chain: Exclude< + CosmWasmChainId | CosmWasmChainName, + typeof CHAIN_ID_TERRA | "terra" + >, + address: string +) { + const chainId: CosmWasmChainId = coalesceCosmWasmChainId(chain); + return ( + (isNativeCosmWasmDenom(chainId, address) ? "01" : "00") + + keccak256(Buffer.from(address, "utf-8")).substring(4) + ); +} +// END SDK PATCH + +export function attestFromSeiMsg(asset: string) { + const nonce = Math.round(Math.random() * 100000); + const isNativeAsset = isNativeDenomSei(asset); + return { + create_asset_meta: { + asset_info: isNativeAsset + ? { + native_token: { denom: asset }, + } + : { + token: { + contract_addr: asset, + }, + }, + nonce: nonce, + }, + }; +} + /** * Returns the address of the foreign asset * @param tokenBridgeAddress Address of token bridge contact