Browse Source

clients/js: Added transfer token command

Kevin Peters 2 years ago
parent
commit
817f179b34

+ 7 - 6
clients/js/package-lock.json

@@ -18,6 +18,7 @@
         "@injectivelabs/utils": "^1.10.5",
         "@injectivelabs/utils": "^1.10.5",
         "@mysten/sui.js": "^0.32.2",
         "@mysten/sui.js": "^0.32.2",
         "@sei-js/core": "^1.3.2",
         "@sei-js/core": "^1.3.2",
+        "@solana/spl-token": "^0.3.5",
         "@solana/web3.js": "^1.22.0",
         "@solana/web3.js": "^1.22.0",
         "@terra-money/terra.js": "^3.1.3",
         "@terra-money/terra.js": "^3.1.3",
         "@types/config": "^3.3.0",
         "@types/config": "^3.3.0",
@@ -3223,9 +3224,9 @@
       }
       }
     },
     },
     "node_modules/@solana/spl-token": {
     "node_modules/@solana/spl-token": {
-      "version": "0.3.7",
-      "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.7.tgz",
-      "integrity": "sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.5.tgz",
+      "integrity": "sha512-0bGC6n415lGjKu02gkLOIpP1wzndSP0SHwN9PefJ+wKAhmfU1rl3AV1Pa41uap2kzSCD6F9642ngNO8KXPvh/g==",
       "dependencies": {
       "dependencies": {
         "@solana/buffer-layout": "^4.0.0",
         "@solana/buffer-layout": "^4.0.0",
         "@solana/buffer-layout-utils": "^0.2.0",
         "@solana/buffer-layout-utils": "^0.2.0",
@@ -10594,9 +10595,9 @@
       }
       }
     },
     },
     "@solana/spl-token": {
     "@solana/spl-token": {
-      "version": "0.3.7",
-      "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.7.tgz",
-      "integrity": "sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.5.tgz",
+      "integrity": "sha512-0bGC6n415lGjKu02gkLOIpP1wzndSP0SHwN9PefJ+wKAhmfU1rl3AV1Pa41uap2kzSCD6F9642ngNO8KXPvh/g==",
       "requires": {
       "requires": {
         "@solana/buffer-layout": "^4.0.0",
         "@solana/buffer-layout": "^4.0.0",
         "@solana/buffer-layout-utils": "^0.2.0",
         "@solana/buffer-layout-utils": "^0.2.0",

+ 1 - 0
clients/js/package.json

@@ -38,6 +38,7 @@
     "@injectivelabs/utils": "^1.10.5",
     "@injectivelabs/utils": "^1.10.5",
     "@mysten/sui.js": "^0.32.2",
     "@mysten/sui.js": "^0.32.2",
     "@sei-js/core": "^1.3.2",
     "@sei-js/core": "^1.3.2",
+    "@solana/spl-token": "^0.3.5",
     "@solana/web3.js": "^1.22.0",
     "@solana/web3.js": "^1.22.0",
     "@terra-money/terra.js": "^3.1.3",
     "@terra-money/terra.js": "^3.1.3",
     "@types/config": "^3.3.0",
     "@types/config": "^3.3.0",

+ 63 - 16
clients/js/src/algorand.ts

@@ -2,11 +2,16 @@ import {
   _submitVAAAlgorand,
   _submitVAAAlgorand,
   signSendAndConfirmAlgorand,
   signSendAndConfirmAlgorand,
 } from "@certusone/wormhole-sdk/lib/esm/algorand";
 } from "@certusone/wormhole-sdk/lib/esm/algorand";
-import { CONTRACTS } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
+import {
+  CONTRACTS,
+  ChainName,
+} from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import { Account, Algodv2, mnemonicToSecretKey } from "algosdk";
 import { Account, Algodv2, mnemonicToSecretKey } from "algosdk";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Network } from "./utils";
 import { Network } from "./utils";
 import { Payload, impossible } from "./vaa";
 import { Payload, impossible } from "./vaa";
+import { transferFromAlgorand } from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import { tryNativeToHexString } from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 export async function execute_algorand(
 export async function execute_algorand(
   payload: Payload,
   payload: Payload,
@@ -25,16 +30,6 @@ export async function execute_algorand(
 
 
   const contracts = CONTRACTS[network][chainName];
   const contracts = CONTRACTS[network][chainName];
   console.log("contracts", contracts);
   console.log("contracts", contracts);
-  const ALGORAND_HOST = {
-    algodToken: "",
-    algodServer: rpc,
-    algodPort: "",
-  };
-  if (network === "DEVNET") {
-    ALGORAND_HOST.algodToken =
-      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
-    ALGORAND_HOST.algodPort = "4001";
-  }
 
 
   let target_contract: string;
   let target_contract: string;
   switch (payload.module) {
   switch (payload.module) {
@@ -127,11 +122,7 @@ export async function execute_algorand(
 
 
   const target = BigInt(parseInt(target_contract));
   const target = BigInt(parseInt(target_contract));
   const CORE_ID = BigInt(parseInt(contracts.core));
   const CORE_ID = BigInt(parseInt(contracts.core));
-  const algodClient = new Algodv2(
-    ALGORAND_HOST.algodToken,
-    ALGORAND_HOST.algodServer,
-    ALGORAND_HOST.algodPort
-  );
+  const algodClient = getClient(network, rpc);
   const algoWallet: Account = mnemonicToSecretKey(key);
   const algoWallet: Account = mnemonicToSecretKey(key);
 
 
   // Create transaction
   // Create transaction
@@ -147,3 +138,59 @@ export async function execute_algorand(
   const result = await signSendAndConfirmAlgorand(algodClient, txs, algoWallet);
   const result = await signSendAndConfirmAlgorand(algodClient, txs, algoWallet);
   console.log("Confirmed in round:", result["confirmed-round"]);
   console.log("Confirmed in round:", result["confirmed-round"]);
 }
 }
+
+export async function transferAlgorand(
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const { key } = NETWORKS[network].algorand;
+  if (!key) {
+    throw Error(`No ${network} key defined for Algorand`);
+  }
+  const contracts = CONTRACTS[network].algorand;
+  const client = getClient(network, rpc);
+  const wallet: Account = mnemonicToSecretKey(key);
+  const CORE_ID = BigInt(parseInt(contracts.core));
+  const TOKEN_BRIDGE_ID = BigInt(parseInt(contracts.token_bridge));
+  const recipient = tryNativeToHexString(dstAddress, dstChain);
+  if (!recipient) {
+    throw new Error("Failed to convert recipient address");
+  }
+  const assetId = tokenAddress === "native" ? BigInt(0) : BigInt(tokenAddress);
+  const txs = await transferFromAlgorand(
+    client,
+    TOKEN_BRIDGE_ID,
+    CORE_ID,
+    wallet.addr,
+    assetId,
+    BigInt(amount),
+    recipient,
+    dstChain,
+    BigInt(0)
+  );
+  const result = await signSendAndConfirmAlgorand(client, txs, wallet);
+  console.log("Confirmed in round:", result["confirmed-round"]);
+}
+
+function getClient(network: Network, rpc: string) {
+  const ALGORAND_HOST = {
+    algodToken: "",
+    algodServer: rpc,
+    algodPort: "",
+  };
+  if (network === "DEVNET") {
+    ALGORAND_HOST.algodToken =
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+    ALGORAND_HOST.algodPort = "4001";
+  }
+  const client = new Algodv2(
+    ALGORAND_HOST.algodToken,
+    ALGORAND_HOST.algodServer,
+    ALGORAND_HOST.algodPort
+  );
+  return client;
+}

+ 45 - 1
clients/js/src/aptos.ts

@@ -1,9 +1,11 @@
 import {
 import {
   CONTRACTS,
   CONTRACTS,
   ChainId,
   ChainId,
+  ChainName,
   assertChain,
   assertChain,
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
-import { AptosAccount, AptosClient, BCS, TxnBuilderTypes } from "aptos";
+import { transferFromAptos } from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import { AptosAccount, AptosClient, BCS, TxnBuilderTypes, Types } from "aptos";
 import { ethers } from "ethers";
 import { ethers } from "ethers";
 import { sha3_256 } from "js-sha3";
 import { sha3_256 } from "js-sha3";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
@@ -11,6 +13,10 @@ import { Network } from "./utils";
 import { Payload, impossible } from "./vaa";
 import { Payload, impossible } from "./vaa";
 import { CHAINS, ensureHexPrefix } from "@certusone/wormhole-sdk";
 import { CHAINS, ensureHexPrefix } from "@certusone/wormhole-sdk";
 import { TokenBridgeState } from "@certusone/wormhole-sdk/lib/esm/aptos/types";
 import { TokenBridgeState } from "@certusone/wormhole-sdk/lib/esm/aptos/types";
+import {
+  generateSignAndSubmitEntryFunction,
+  tryNativeToUint8Array,
+} from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 export async function execute_aptos(
 export async function execute_aptos(
   payload: Payload,
   payload: Payload,
@@ -236,6 +242,44 @@ export async function execute_aptos(
   }
   }
 }
 }
 
 
+export async function transferAptos(
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const { key } = NETWORKS[network].aptos;
+  if (!key) {
+    throw new Error("No key for aptos");
+  }
+  rpc = rpc ?? NETWORKS[network].aptos.rpc;
+  if (!rpc) {
+    throw new Error("No rpc for aptos");
+  }
+  const { token_bridge } = CONTRACTS[network].aptos;
+  if (!token_bridge) {
+    throw new Error("token bridge contract is undefined");
+  }
+  const account = new AptosAccount(new Uint8Array(Buffer.from(key, "hex")));
+  const client = new AptosClient(rpc);
+  const transferPayload = transferFromAptos(
+    token_bridge,
+    tokenAddress === "native" ? "0x1::aptos_coin::AptosCoin" : tokenAddress,
+    amount,
+    dstChain,
+    tryNativeToUint8Array(dstAddress, dstChain)
+  );
+  const tx = (await generateSignAndSubmitEntryFunction(
+    client,
+    account,
+    transferPayload
+  )) as Types.UserTransaction;
+  await client.waitForTransaction(tx.hash);
+  console.log(`hash: ${tx.hash}`);
+}
+
 export function deriveWrappedAssetAddress(
 export function deriveWrappedAssetAddress(
   token_bridge_address: Uint8Array, // 32 bytes
   token_bridge_address: Uint8Array, // 32 bytes
   origin_chain: ChainId,
   origin_chain: ChainId,

+ 1 - 1
clients/js/src/chains/sui/submit.ts

@@ -129,7 +129,7 @@ export const submit = async (
             console.log(`  Type ${getWrappedCoinType(coinPackageId)}`);
             console.log(`  Type ${getWrappedCoinType(coinPackageId)}`);
 
 
             if (!rpc && network !== "DEVNET") {
             if (!rpc && network !== "DEVNET") {
-              // Wait for wrapped asset creation to be propogated to other
+              // Wait for wrapped asset creation to be propagated to other
               // nodes in case this complete registration call is load balanced
               // nodes in case this complete registration call is load balanced
               // to another node.
               // to another node.
               await sleep(5000);
               await sleep(5000);

+ 53 - 0
clients/js/src/chains/sui/transfer.ts

@@ -0,0 +1,53 @@
+import { transferFromSui } from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import {
+  executeTransactionBlock,
+  getProvider,
+  getSigner,
+  setMaxGasBudgetDevnet,
+} from "./utils";
+import {
+  CONTRACTS,
+  ChainName,
+  Network,
+  tryNativeToUint8Array,
+} from "@certusone/wormhole-sdk/lib/esm/utils";
+
+export async function transferSui(
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const { core, token_bridge } = CONTRACTS[network]["sui"];
+  if (!core) {
+    throw Error("Core bridge object ID is undefined");
+  }
+  if (!token_bridge) {
+    throw new Error("Token bridge object ID is undefined");
+  }
+  const provider = getProvider(network, rpc);
+  const signer = getSigner(provider, network);
+  const owner = await signer.getAddress();
+  const coinType = tokenAddress === "native" ? "0x2::sui::SUI" : tokenAddress;
+  const coins = (
+    await provider.getCoins({
+      owner,
+      coinType,
+    })
+  ).data;
+  const tx = await transferFromSui(
+    provider,
+    core,
+    token_bridge,
+    coins,
+    coinType,
+    BigInt(amount),
+    dstChain,
+    tryNativeToUint8Array(dstAddress, dstChain)
+  );
+  setMaxGasBudgetDevnet(network, tx);
+  const result = await executeTransactionBlock(signer, tx);
+  console.log(JSON.stringify(result));
+}

+ 146 - 0
clients/js/src/cmds/transfer.ts

@@ -0,0 +1,146 @@
+import {
+  isCosmWasmChain,
+  isEVMChain,
+  isTerraChain,
+} from "@certusone/wormhole-sdk/lib/esm/utils/consts";
+import yargs from "yargs";
+import { impossible } from "../vaa";
+import { transferEVM } from "../evm";
+import { CHAIN_NAME_CHOICES, NETWORK_OPTIONS, NETWORKS } from "../consts";
+import { assertNetwork } from "../utils";
+import { transferTerra } from "../terra";
+import { transferInjective } from "../injective";
+import { transferXpla } from "../xpla";
+import { transferSolana } from "../solana";
+import { transferAlgorand } from "../algorand";
+import { transferNear } from "../near";
+import { transferSui } from "../chains/sui/transfer";
+import { transferAptos } from "../aptos";
+
+export const command = "transfer";
+export const desc = "Transfer a token";
+export const builder = (y: typeof yargs) =>
+  y
+    .option("src-chain", {
+      describe: "source chain",
+      choices: CHAIN_NAME_CHOICES,
+      demandOption: true,
+    })
+    .option("dst-chain", {
+      describe: "destination chain",
+      choices: CHAIN_NAME_CHOICES,
+      demandOption: true,
+    })
+    .option("dst-addr", {
+      describe: "destination address",
+      type: "string",
+      demandOption: true,
+    })
+    .option("token-addr", {
+      describe: "token address",
+      type: "string",
+      default: "native",
+      defaultDescription: "native token",
+      demandOption: false,
+    })
+    .option("amount", {
+      describe: "token amount",
+      type: "string",
+      demandOption: true,
+    })
+    .option("network", NETWORK_OPTIONS)
+    .option("rpc", {
+      describe: "RPC endpoint",
+      type: "string",
+      demandOption: false,
+    });
+
+export const handler = async (
+  argv: Awaited<ReturnType<typeof builder>["argv"]>
+) => {
+  const srcChain = argv["src-chain"];
+  const dstChain = argv["dst-chain"];
+  if (srcChain === "unset") {
+    throw new Error("source chain is unset");
+  }
+  if (dstChain === "unset") {
+    throw new Error("destination chain is unset");
+  }
+  // TODO: support transfers to sei
+  if (dstChain === "sei") {
+    throw new Error("transfer to sei currently unsupported");
+  }
+  if (srcChain === dstChain) {
+    throw new Error("source and destination chains can't be the same");
+  }
+  const amount = argv.amount;
+  if (BigInt(amount) <= 0) {
+    throw new Error("amount must be greater than 0");
+  }
+  const tokenAddr = argv["token-addr"];
+  if (tokenAddr === "native" && isCosmWasmChain(srcChain)) {
+    throw new Error(`token-addr must be specified for ${srcChain}`);
+  }
+  const dstAddr = argv["dst-addr"];
+  const network = argv.network.toUpperCase();
+  assertNetwork(network);
+  const rpc = argv.rpc ?? NETWORKS[network][srcChain].rpc;
+  if (!rpc) {
+    throw new Error(`No ${network} rpc defined for ${srcChain}`);
+  }
+  if (isEVMChain(srcChain)) {
+    await transferEVM(
+      srcChain,
+      dstChain,
+      dstAddr,
+      tokenAddr,
+      amount,
+      network,
+      rpc
+    );
+  } else if (isTerraChain(srcChain)) {
+    await transferTerra(
+      srcChain,
+      dstChain,
+      dstAddr,
+      tokenAddr,
+      amount,
+      network,
+      rpc
+    );
+  } else if (srcChain === "solana" || srcChain === "pythnet") {
+    await transferSolana(
+      srcChain,
+      dstChain,
+      dstAddr,
+      tokenAddr,
+      amount,
+      network,
+      rpc
+    );
+  } else if (srcChain === "algorand") {
+    await transferAlgorand(dstChain, dstAddr, tokenAddr, amount, network, rpc);
+  } else if (srcChain === "near") {
+    await transferNear(dstChain, dstAddr, tokenAddr, amount, network, rpc);
+  } else if (srcChain === "injective") {
+    await transferInjective(dstChain, dstAddr, tokenAddr, amount, network, rpc);
+  } else if (srcChain === "xpla") {
+    await transferXpla(dstChain, dstAddr, tokenAddr, amount, network, rpc);
+  } else if (srcChain === "sei") {
+    throw new Error("sei is not supported yet");
+  } else if (srcChain === "osmosis") {
+    throw Error("OSMOSIS is not supported yet");
+  } else if (srcChain === "sui") {
+    await transferSui(dstChain, dstAddr, tokenAddr, amount, network, rpc);
+  } else if (srcChain === "aptos") {
+    await transferAptos(dstChain, dstAddr, tokenAddr, amount, network, rpc);
+  } else if (srcChain === "wormchain") {
+    throw Error("Wormchain is not supported yet");
+  } else if (srcChain === "btc") {
+    throw Error("btc is not supported yet");
+  } else {
+    // If you get a type error here, hover over `chain`'s type and it tells you
+    // which cases are not handled
+    impossible(srcChain);
+  }
+};

+ 4 - 0
clients/js/src/consts/yargs.ts

@@ -42,3 +42,7 @@ export const CHAIN_ID_OR_NAME_CHOICES = [
   ...Object.keys(CHAINS),
   ...Object.keys(CHAINS),
   ...Object.values(CHAINS),
   ...Object.values(CHAINS),
 ] as (ChainName | ChainId)[];
 ] as (ChainName | ChainId)[];
+
+export const CHAIN_NAME_CHOICES = Object.keys(CHAINS).filter(
+  (c) => c !== "unset"
+) as ChainName[];

+ 122 - 50
clients/js/src/evm.ts

@@ -3,16 +3,16 @@ import {
   BridgeImplementation__factory,
   BridgeImplementation__factory,
   Implementation__factory,
   Implementation__factory,
   NFTBridgeImplementation__factory,
   NFTBridgeImplementation__factory,
-  WormholeRelayer__factory
+  WormholeRelayer__factory,
 } from "@certusone/wormhole-sdk/lib/esm/ethers-contracts";
 } from "@certusone/wormhole-sdk/lib/esm/ethers-contracts";
-import {
-  getWormholeRelayerAddress
-} from "@certusone/wormhole-sdk/lib/esm/relayer"
+import { getWormholeRelayerAddress } from "@certusone/wormhole-sdk/lib/esm/relayer";
 import {
 import {
   CHAINS,
   CHAINS,
   CONTRACTS,
   CONTRACTS,
+  ChainName,
   Contracts,
   Contracts,
   EVMChainName,
   EVMChainName,
+  toChainId,
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import axios from "axios";
 import axios from "axios";
 import { ethers } from "ethers";
 import { ethers } from "ethers";
@@ -20,6 +20,13 @@ import { solidityKeccak256 } from "ethers/lib/utils";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Network } from "./utils";
 import { Network } from "./utils";
 import { Encoding, Payload, encode, impossible, typeWidth } from "./vaa";
 import { Encoding, Payload, encode, impossible, typeWidth } from "./vaa";
+import {
+  approveEth,
+  getAllowanceEth,
+  transferFromEth,
+  transferFromEthNative,
+} from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import { tryNativeToUint8Array } from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 const _IMPLEMENTATION_SLOT =
 const _IMPLEMENTATION_SLOT =
   "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
   "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
@@ -264,26 +271,7 @@ export async function getImplementation(
   )[0];
   )[0];
 }
 }
 
 
-export async function execute_evm(
-  payload: Payload,
-  vaa: Buffer,
-  network: Network,
-  chain: EVMChainName,
-  contract_address: string | undefined,
-  _rpc: string | undefined
-) {
-  const n = NETWORKS[network][chain];
-  const rpc: string | undefined = _rpc ?? n.rpc;
-  if (!rpc) {
-    throw Error(`No ${network} rpc defined for ${chain} (see networks.ts)`);
-  }
-
-  if (!n.key) {
-    throw Error(`No ${network} key defined for ${chain} (see networks.ts)`);
-  }
-
-  const key: string = n.key;
-  const contracts: Contracts = CONTRACTS[network][chain];
+async function getSigner(chain: EVMChainName, key: string, rpc: string) {
   let provider: ethers.providers.JsonRpcProvider;
   let provider: ethers.providers.JsonRpcProvider;
   let signer: ethers.Wallet;
   let signer: ethers.Wallet;
   if (chain === "celo") {
   if (chain === "celo") {
@@ -294,7 +282,6 @@ export async function execute_evm(
     provider = new ethers.providers.JsonRpcProvider(rpc);
     provider = new ethers.providers.JsonRpcProvider(rpc);
     signer = new ethers.Wallet(key, provider);
     signer = new ethers.Wallet(key, provider);
   }
   }
-
   // Here we apply a set of chain-specific overrides.
   // Here we apply a set of chain-specific overrides.
   // NOTE: some of these might have only been tested on mainnet. If it fails in
   // NOTE: some of these might have only been tested on mainnet. If it fails in
   // testnet (or devnet), they might require additional guards
   // testnet (or devnet), they might require additional guards
@@ -310,6 +297,34 @@ export async function execute_evm(
   } else if (chain === "klaytn" || chain === "fantom") {
   } else if (chain === "klaytn" || chain === "fantom") {
     overrides = { gasPrice: (await signer.getGasPrice()).toString() };
     overrides = { gasPrice: (await signer.getGasPrice()).toString() };
   }
   }
+  return {
+    signer,
+    provider,
+    overrides,
+  };
+}
+
+export async function execute_evm(
+  payload: Payload,
+  vaa: Buffer,
+  network: Network,
+  chain: EVMChainName,
+  contract_address: string | undefined,
+  _rpc: string | undefined
+) {
+  const n = NETWORKS[network][chain];
+  const rpc: string | undefined = _rpc ?? n.rpc;
+  if (!rpc) {
+    throw Error(`No ${network} rpc defined for ${chain} (see networks.ts)`);
+  }
+
+  if (!n.key) {
+    throw Error(`No ${network} key defined for ${chain} (see networks.ts)`);
+  }
+
+  const key: string = n.key;
+  const contracts: Contracts = CONTRACTS[network][chain];
+  const { signer, overrides } = await getSigner(chain, key, rpc);
 
 
   switch (payload.module) {
   switch (payload.module) {
     case "Core": {
     case "Core": {
@@ -434,38 +449,95 @@ export async function execute_evm(
       break;
       break;
     }
     }
     case "WormholeRelayer":
     case "WormholeRelayer":
-        contract_address = contract_address
+      contract_address = contract_address
         ? contract_address
         ? contract_address
         : getWormholeRelayerAddress(chain, network);
         : getWormholeRelayerAddress(chain, network);
-        if (contract_address === undefined) {
-          throw Error(`Unknown Wormhole Relayer contract on ${network} for ${chain}`)
-        }
-        let rb = WormholeRelayer__factory.connect(contract_address, signer)
-        switch (payload.type) {
-          case "ContractUpgrade":
-            console.log("Upgrading contract")
-            console.log("Hash: " + (await rb.submitContractUpgrade(vaa, overrides)).hash)
-            console.log("Don't forget to verify the new implementation! See ethereum/VERIFY.md for instructions")
-            break
-          case "RegisterChain":
-            console.log("Registering chain")
-            console.log("Hash: " + (await rb.registerWormholeRelayerContract(vaa, overrides)).hash)
-            break
-          case "SetDefaultDeliveryProvider":
-            console.log("Setting default relay provider")
-            console.log("Hash: " + (await rb.setDefaultDeliveryProvider(vaa, overrides)).hash)
-            break
-          default:
-            impossible(payload)
-            break
-  
-        }
-        break
+      if (contract_address === undefined) {
+        throw Error(
+          `Unknown Wormhole Relayer contract on ${network} for ${chain}`
+        );
+      }
+      let rb = WormholeRelayer__factory.connect(contract_address, signer);
+      switch (payload.type) {
+        case "ContractUpgrade":
+          console.log("Upgrading contract");
+          console.log(
+            "Hash: " + (await rb.submitContractUpgrade(vaa, overrides)).hash
+          );
+          console.log(
+            "Don't forget to verify the new implementation! See ethereum/VERIFY.md for instructions"
+          );
+          break;
+        case "RegisterChain":
+          console.log("Registering chain");
+          console.log(
+            "Hash: " +
+              (await rb.registerWormholeRelayerContract(vaa, overrides)).hash
+          );
+          break;
+        case "SetDefaultDeliveryProvider":
+          console.log("Setting default relay provider");
+          console.log(
+            "Hash: " +
+              (await rb.setDefaultDeliveryProvider(vaa, overrides)).hash
+          );
+          break;
+        default:
+          impossible(payload);
+          break;
+      }
+      break;
     default:
     default:
       impossible(payload);
       impossible(payload);
   }
   }
 }
 }
 
 
+export async function transferEVM(
+  srcChain: EVMChainName,
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const n = NETWORKS[network][srcChain];
+  if (!n.key) {
+    throw Error(`No ${network} key defined for ${srcChain} (see networks.ts)`);
+  }
+  const { token_bridge } = CONTRACTS[network][srcChain];
+  if (!token_bridge) {
+    throw Error(`Unknown token bridge contract on ${network} for ${srcChain}`);
+  }
+  const { signer, overrides } = await getSigner(srcChain, n.key, rpc);
+  let tx;
+  if (tokenAddress === "native") {
+    tx = await transferFromEthNative(
+      token_bridge,
+      signer,
+      amount,
+      toChainId(dstChain),
+      tryNativeToUint8Array(dstAddress, dstChain)
+    );
+  } else {
+    const allowance = await getAllowanceEth(token_bridge, tokenAddress, signer);
+    if (allowance.lt(amount)) {
+      await approveEth(token_bridge, tokenAddress, signer, amount, overrides);
+    }
+    tx = await transferFromEth(
+      token_bridge,
+      signer,
+      tokenAddress,
+      amount,
+      dstChain,
+      tryNativeToUint8Array(dstAddress, dstChain),
+      undefined,
+      overrides
+    );
+  }
+  console.log(`Hash: ${tx.transactionHash}`);
+}
+
 /**
 /**
  *
  *
  * Hijack a core contract. This function is useful when working with a mainnet
  * Hijack a core contract. This function is useful when working with a mainnet

+ 56 - 2
clients/js/src/injective.ts

@@ -1,6 +1,7 @@
 import {
 import {
   CHAINS,
   CHAINS,
   CONTRACTS,
   CONTRACTS,
+  ChainName,
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import {
 import {
   getNetworkInfo,
   getNetworkInfo,
@@ -11,6 +12,7 @@ import {
   ChainRestAuthApi,
   ChainRestAuthApi,
   createTransaction,
   createTransaction,
   MsgExecuteContractCompat,
   MsgExecuteContractCompat,
+  Msgs,
   PrivateKey,
   PrivateKey,
   TxGrpcApi,
   TxGrpcApi,
 } from "@injectivelabs/sdk-ts";
 } from "@injectivelabs/sdk-ts";
@@ -19,6 +21,8 @@ import { fromUint8Array } from "js-base64";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Network } from "./utils";
 import { Network } from "./utils";
 import { impossible, Payload } from "./vaa";
 import { impossible, Payload } from "./vaa";
+import { transferFromInjective } from "@certusone/wormhole-sdk/lib/esm/token_bridge/injective";
+import { tryNativeToUint8Array } from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 export async function execute_injective(
 export async function execute_injective(
   payload: Payload,
   payload: Payload,
@@ -157,11 +161,61 @@ export async function execute_injective(
   });
   });
   console.log("transaction:", transaction);
   console.log("transaction:", transaction);
 
 
+  await signAndSendTx(walletPK, network, transaction);
+}
+
+export async function transferInjective(
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  if (network === "DEVNET") {
+    throw new Error("Injective is not supported in DEVNET");
+  }
+  const chain = "injective";
+  const { key } = NETWORKS[network][chain];
+  if (!key) {
+    throw Error(`No ${network} key defined for Injective`);
+  }
+  const { token_bridge } = CONTRACTS[network][chain];
+  if (token_bridge == undefined) {
+    throw Error(`Unknown token bridge contract on ${network} for ${chain}`);
+  }
+
+  const walletPK = PrivateKey.fromMnemonic(key);
+  const walletInjAddr = walletPK.toBech32();
+
+  const msgs = await transferFromInjective(
+    walletInjAddr,
+    token_bridge,
+    tokenAddress,
+    amount,
+    dstChain,
+    tryNativeToUint8Array(dstAddress, dstChain)
+  );
+
+  await signAndSendTx(walletPK, network, msgs);
+}
+
+async function signAndSendTx(
+  walletPK: PrivateKey,
+  network: string,
+  msgs: Msgs | Msgs[]
+) {
+  const endPoint =
+    network === "MAINNET"
+      ? InjectiveNetwork.MainnetK8s
+      : InjectiveNetwork.TestnetK8s;
+  const networkInfo = getNetworkInfo(endPoint);
+  const walletPublicKey = walletPK.toPublicKey().toBase64();
   const accountDetails = await new ChainRestAuthApi(
   const accountDetails = await new ChainRestAuthApi(
     networkInfo.rest
     networkInfo.rest
-  ).fetchAccount(walletInjAddr);
+  ).fetchAccount(walletPK.toBech32());
   const { signBytes, txRaw } = createTransaction({
   const { signBytes, txRaw } = createTransaction({
-    message: transaction,
+    message: msgs,
     memo: "",
     memo: "",
     fee: getStdFee((parseInt(DEFAULT_STD_FEE.gas, 10) * 2.5).toString()),
     fee: getStdFee((parseInt(DEFAULT_STD_FEE.gas, 10) * 2.5).toString()),
     pubKey: walletPublicKey,
     pubKey: walletPublicKey,

+ 2 - 0
clients/js/src/main.ts

@@ -15,6 +15,7 @@ import * as parse from "./cmds/parse";
 import * as recover from "./cmds/recover";
 import * as recover from "./cmds/recover";
 import * as submit from "./cmds/submit";
 import * as submit from "./cmds/submit";
 import * as sui from "./cmds/sui";
 import * as sui from "./cmds/sui";
+import * as transfer from "./cmds/transfer";
 import * as verifyVaa from "./cmds/verifyVaa";
 import * as verifyVaa from "./cmds/verifyVaa";
 
 
 yargs(hideBin(process.argv))
 yargs(hideBin(process.argv))
@@ -30,6 +31,7 @@ yargs(hideBin(process.argv))
   .command(recover)
   .command(recover)
   .command(submit)
   .command(submit)
   .command(sui)
   .command(sui)
+  .command(transfer)
   .command(verifyVaa)
   .command(verifyVaa)
   .strict()
   .strict()
   .demandCommand().argv;
   .demandCommand().argv;

+ 68 - 1
clients/js/src/near.ts

@@ -1,10 +1,18 @@
-import { CONTRACTS } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
+import {
+  ChainName,
+  CONTRACTS,
+} from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import BN from "bn.js";
 import BN from "bn.js";
 import { Account, connect, KeyPair } from "near-api-js";
 import { Account, connect, KeyPair } from "near-api-js";
 import { InMemoryKeyStore } from "near-api-js/lib/key_stores";
 import { InMemoryKeyStore } from "near-api-js/lib/key_stores";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Network } from "./utils";
 import { Network } from "./utils";
 import { impossible, Payload } from "./vaa";
 import { impossible, Payload } from "./vaa";
+import {
+  transferNearFromNear,
+  transferTokenFromNear,
+} from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import { tryNativeToUint8Array } from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 export const execute_near = async (
 export const execute_near = async (
   payload: Payload,
   payload: Payload,
@@ -142,3 +150,62 @@ export const execute_near = async (
   const txHash = result1.transaction.hash + ":" + result2.transaction.hash;
   const txHash = result1.transaction.hash + ":" + result2.transaction.hash;
   console.log("Hash: " + txHash);
   console.log("Hash: " + txHash);
 };
 };
+
+export async function transferNear(
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const { key, networkId, deployerAccount } = NETWORKS[network].near;
+  if (!key) {
+    throw Error(`No ${network} key defined for NEAR`);
+  }
+  const { core, token_bridge } = CONTRACTS[network].near;
+  if (core === undefined) {
+    throw Error(`Unknown core contract on ${network} for NEAR`);
+  }
+  if (token_bridge === undefined) {
+    throw Error(`Unknown token bridge contract on ${network} for NEAR`);
+  }
+  const keyStore = new InMemoryKeyStore();
+  keyStore.setKey(networkId, deployerAccount, KeyPair.fromString(key));
+  const near = await connect({
+    keyStore,
+    networkId,
+    nodeUrl: rpc,
+    headers: {},
+  });
+  const nearAccount = new Account(near.connection, deployerAccount);
+  if (tokenAddress === "native") {
+    const msg = await transferNearFromNear(
+      near.connection.provider,
+      core,
+      token_bridge,
+      BigInt(amount),
+      tryNativeToUint8Array(dstAddress, dstChain),
+      dstChain,
+      BigInt(0)
+    );
+    const result = await nearAccount.functionCall(msg);
+    console.log(result.transaction.hash);
+  } else {
+    const msgs = await transferTokenFromNear(
+      near.connection.provider,
+      nearAccount.accountId,
+      core,
+      token_bridge,
+      tokenAddress,
+      BigInt(amount),
+      tryNativeToUint8Array(dstAddress, dstChain),
+      dstChain,
+      BigInt(0)
+    );
+    for (const msg of msgs) {
+      const result = await nearAccount.functionCall(msg);
+      console.log(result.transaction.hash);
+    }
+  }
+}

+ 90 - 2
clients/js/src/solana.ts

@@ -19,15 +19,25 @@ import {
 import {
 import {
   CHAINS,
   CHAINS,
   CONTRACTS,
   CONTRACTS,
+  ChainName,
+  Network,
   SolanaChainName,
   SolanaChainName,
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import * as web3s from "@solana/web3.js";
 import * as web3s from "@solana/web3.js";
 import base58 from "bs58";
 import base58 from "bs58";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Payload, VAA, impossible } from "./vaa";
 import { Payload, VAA, impossible } from "./vaa";
-import { ChainName, hexToUint8Array } from "@certusone/wormhole-sdk";
 import { getEmitterAddress } from "./emitter";
 import { getEmitterAddress } from "./emitter";
-import { Network } from "./utils";
+import {
+  transferFromSolana,
+  transferNativeSol,
+} from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import {
+  hexToUint8Array,
+  tryNativeToUint8Array,
+} from "@certusone/wormhole-sdk/lib/esm/utils";
+import { PublicKey } from "@solana/web3.js";
+import { getAssociatedTokenAddress } from "@solana/spl-token";
 
 
 export async function execute_solana(
 export async function execute_solana(
   v: VAA<Payload>,
   v: VAA<Payload>,
@@ -217,6 +227,84 @@ export async function execute_solana(
   console.log("SIGNATURE", signature);
   console.log("SIGNATURE", signature);
 }
 }
 
 
+export async function transferSolana(
+  srcChain: SolanaChainName,
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const { key } = NETWORKS[network][srcChain];
+  if (!key) {
+    throw Error(`No ${network} key defined for ${srcChain}`);
+  }
+
+  const connection = setupConnection(rpc);
+  const keypair = web3s.Keypair.fromSecretKey(base58.decode(key));
+
+  const { core, token_bridge } = CONTRACTS[network][srcChain];
+  if (!core) {
+    throw new Error(
+      `Core bridge address not defined for ${srcChain} ${network}`
+    );
+  }
+  if (!token_bridge) {
+    throw new Error(
+      `Token bridge address not defined for ${srcChain} ${network}`
+    );
+  }
+
+  const bridgeId = new web3s.PublicKey(core);
+  const tokenBridgeId = new web3s.PublicKey(token_bridge);
+  const payerAddress = keypair.publicKey.toString();
+
+  let transaction;
+  if (tokenAddress === "native") {
+    transaction = await transferNativeSol(
+      connection,
+      bridgeId,
+      tokenBridgeId,
+      payerAddress,
+      BigInt(amount),
+      tryNativeToUint8Array(dstAddress, dstChain),
+      dstChain
+    );
+  } else {
+    // find the associated token account
+    const fromAddress = (
+      await getAssociatedTokenAddress(
+        new PublicKey(tokenAddress),
+        keypair.publicKey
+      )
+    ).toString();
+    transaction = await transferFromSolana(
+      connection,
+      bridgeId,
+      tokenBridgeId,
+      payerAddress,
+      fromAddress,
+      tokenAddress, // mintAddress
+      BigInt(amount),
+      tryNativeToUint8Array(dstAddress, dstChain),
+      dstChain
+    );
+  }
+
+  // sign, send, and confirm transaction
+  transaction.partialSign(keypair);
+  const signature = await connection.sendRawTransaction(
+    transaction.serialize()
+  );
+  await connection.confirmTransaction(signature);
+  const info = await connection.getTransaction(signature);
+  if (!info) {
+    throw new Error("An error occurred while fetching the transaction info");
+  }
+  console.log("SIGNATURE", signature);
+}
+
 const setupConnection = (rpc: string): web3s.Connection =>
 const setupConnection = (rpc: string): web3s.Connection =>
   new web3s.Connection(rpc, "confirmed");
   new web3s.Connection(rpc, "confirmed");
 
 

+ 55 - 2
clients/js/src/terra.ts

@@ -1,6 +1,7 @@
 import {
 import {
   CHAINS,
   CHAINS,
   CONTRACTS,
   CONTRACTS,
+  ChainName,
   TerraChainName,
   TerraChainName,
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import {
 import {
@@ -9,12 +10,15 @@ import {
   LCDClient,
   LCDClient,
   MnemonicKey,
   MnemonicKey,
   MsgExecuteContract,
   MsgExecuteContract,
+  Wallet,
 } from "@terra-money/terra.js";
 } from "@terra-money/terra.js";
 import axios from "axios";
 import axios from "axios";
 import { fromUint8Array } from "js-base64";
 import { fromUint8Array } from "js-base64";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Network } from "./utils";
 import { Network } from "./utils";
 import { Payload, impossible } from "./vaa";
 import { Payload, impossible } from "./vaa";
+import { transferFromTerra } from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import { tryNativeToUint8Array } from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 export async function execute_terra(
 export async function execute_terra(
   payload: Payload,
   payload: Payload,
@@ -152,6 +156,55 @@ export async function execute_terra(
     { uluna: 1000 }
     { uluna: 1000 }
   );
   );
 
 
+  await signAndSendTx(terra, wallet, [transaction]);
+}
+
+export async function transferTerra(
+  srcChain: TerraChainName,
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const n = NETWORKS[network][srcChain];
+  if (!n.key) {
+    throw Error(`No ${network} key defined for ${srcChain} (see networks.ts)`);
+  }
+  const { token_bridge } = CONTRACTS[network][srcChain];
+  if (!token_bridge) {
+    throw Error(`Unknown token bridge contract on ${network} for ${srcChain}`);
+  }
+
+  const terra = new LCDClient({
+    URL: rpc,
+    chainID: n.chain_id,
+    isClassic: srcChain === "terra",
+  });
+
+  const wallet = terra.wallet(
+    new MnemonicKey({
+      mnemonic: n.key,
+    })
+  );
+
+  const msgs = await transferFromTerra(
+    wallet.key.accAddress,
+    token_bridge,
+    tokenAddress,
+    amount,
+    dstChain,
+    tryNativeToUint8Array(dstAddress, dstChain)
+  );
+  await signAndSendTx(terra, wallet, msgs);
+}
+
+async function signAndSendTx(
+  terra: LCDClient,
+  wallet: Wallet,
+  msgs: MsgExecuteContract[]
+) {
   const feeDenoms = ["uluna"];
   const feeDenoms = ["uluna"];
   const gasPrices = await axios
   const gasPrices = await axios
     .get("https://terra-classic-fcd.publicnode.com/v1/txs/gas_prices")
     .get("https://terra-classic-fcd.publicnode.com/v1/txs/gas_prices")
@@ -164,7 +217,7 @@ export async function execute_terra(
       },
       },
     ],
     ],
     {
     {
-      msgs: [transaction],
+      msgs,
       memo: "",
       memo: "",
       feeDenoms,
       feeDenoms,
       gasPrices,
       gasPrices,
@@ -173,7 +226,7 @@ export async function execute_terra(
 
 
   return wallet
   return wallet
     .createAndSignTx({
     .createAndSignTx({
-      msgs: [transaction],
+      msgs,
       memo: "",
       memo: "",
       fee: new Fee(
       fee: new Fee(
         feeEstimate.gas_limit,
         feeEstimate.gas_limit,

+ 53 - 3
clients/js/src/xpla.ts

@@ -1,15 +1,21 @@
-import { CONTRACTS } from "@certusone/wormhole-sdk/lib/esm/utils/consts";
+import {
+  CONTRACTS,
+  ChainName,
+} from "@certusone/wormhole-sdk/lib/esm/utils/consts";
 import {
 import {
   Coin,
   Coin,
   Fee,
   Fee,
   LCDClient,
   LCDClient,
   MnemonicKey,
   MnemonicKey,
   MsgExecuteContract,
   MsgExecuteContract,
+  Wallet,
 } from "@xpla/xpla.js";
 } from "@xpla/xpla.js";
 import { fromUint8Array } from "js-base64";
 import { fromUint8Array } from "js-base64";
 import { NETWORKS } from "./consts";
 import { NETWORKS } from "./consts";
 import { Network } from "./utils";
 import { Network } from "./utils";
 import { Payload, impossible } from "./vaa";
 import { Payload, impossible } from "./vaa";
+import { transferFromXpla } from "@certusone/wormhole-sdk/lib/esm/token_bridge/transfer";
+import { tryNativeToUint8Array } from "@certusone/wormhole-sdk/lib/esm/utils";
 
 
 export async function execute_xpla(
 export async function execute_xpla(
   payload: Payload,
   payload: Payload,
@@ -146,6 +152,50 @@ export async function execute_xpla(
     { axpla: "1700000000000000000" }
     { axpla: "1700000000000000000" }
   );
   );
 
 
+  await signAndSendTx(client, wallet, [transaction]);
+}
+
+export async function transferXpla(
+  dstChain: ChainName,
+  dstAddress: string,
+  tokenAddress: string,
+  amount: string,
+  network: Network,
+  rpc: string
+) {
+  const { key, chain_id } = NETWORKS[network].xpla;
+  if (!key) {
+    throw Error(`No ${network} key defined for XPLA`);
+  }
+  const { token_bridge } = CONTRACTS[network].xpla;
+  if (token_bridge == undefined) {
+    throw Error(`Unknown token bridge contract on ${network} for XPLA`);
+  }
+  const client = new LCDClient({
+    URL: rpc,
+    chainID: chain_id,
+  });
+  const wallet = client.wallet(
+    new MnemonicKey({
+      mnemonic: key,
+    })
+  );
+  const msgs = transferFromXpla(
+    wallet.key.accAddress,
+    token_bridge,
+    tokenAddress,
+    amount,
+    dstChain,
+    tryNativeToUint8Array(dstAddress, dstChain)
+  );
+  await signAndSendTx(client, wallet, msgs);
+}
+
+async function signAndSendTx(
+  client: LCDClient,
+  wallet: Wallet,
+  msgs: MsgExecuteContract[]
+) {
   const feeDenoms = ["axpla"];
   const feeDenoms = ["axpla"];
   // const gasPrices = await axios
   // const gasPrices = await axios
   //   .get("https://dimension-lcd.xpla.dev/v1/txs/gas_prices")
   //   .get("https://dimension-lcd.xpla.dev/v1/txs/gas_prices")
@@ -158,7 +208,7 @@ export async function execute_xpla(
       },
       },
     ],
     ],
     {
     {
-      msgs: [transaction],
+      msgs,
       memo: "",
       memo: "",
       feeDenoms,
       feeDenoms,
       // gasPrices,
       // gasPrices,
@@ -167,7 +217,7 @@ export async function execute_xpla(
 
 
   wallet
   wallet
     .createAndSignTx({
     .createAndSignTx({
-      msgs: [transaction],
+      msgs,
       memo: "",
       memo: "",
       fee: new Fee(
       fee: new Fee(
         feeEstimate.gas_limit,
         feeEstimate.gas_limit,

+ 1 - 1
sdk/js/src/token_bridge/transfer.ts

@@ -145,7 +145,7 @@ export async function transferFromEthNative(
   tokenBridgeAddress: string,
   tokenBridgeAddress: string,
   signer: ethers.Signer,
   signer: ethers.Signer,
   amount: ethers.BigNumberish,
   amount: ethers.BigNumberish,
-  recipientChain: ChainId | ChainId,
+  recipientChain: ChainId | ChainName,
   recipientAddress: Uint8Array,
   recipientAddress: Uint8Array,
   relayerFee: ethers.BigNumberish = 0,
   relayerFee: ethers.BigNumberish = 0,
   overrides: PayableOverrides & { from?: string | Promise<string> } = {},
   overrides: PayableOverrides & { from?: string | Promise<string> } = {},