瀏覽代碼

sdk/js: attempt jest tests

Change-Id: I139153994604f0048f690b011048daec4d760a55
Evan Gray 4 年之前
父節點
當前提交
c824a99636
共有 36 個文件被更改,包括 1728 次插入756 次删除
  1. 12 0
      sdk/js/CHANGELOG.md
  2. 7 0
      sdk/js/jestconfig.json
  3. 1025 589
      sdk/js/package-lock.json
  4. 6 2
      sdk/js/package.json
  5. 2 1
      sdk/js/src/bridge/getClaimAddress.ts
  6. 2 1
      sdk/js/src/bridge/getEmitterAddress.ts
  7. 2 3
      sdk/js/src/migration/addLiquidity.ts
  8. 3 3
      sdk/js/src/migration/authorityAddress.ts
  9. 2 3
      sdk/js/src/migration/claimShares.ts
  10. 2 3
      sdk/js/src/migration/createPool.ts
  11. 3 3
      sdk/js/src/migration/fromCustodyAddress.ts
  12. 2 3
      sdk/js/src/migration/migrateTokens.ts
  13. 3 1
      sdk/js/src/migration/parsePool.ts
  14. 3 3
      sdk/js/src/migration/poolAddress.ts
  15. 2 3
      sdk/js/src/migration/removeLiquidity.ts
  16. 3 3
      sdk/js/src/migration/shareMintAddress.ts
  17. 3 3
      sdk/js/src/migration/toCustodyAddress.ts
  18. 2 1
      sdk/js/src/nft_bridge/getForeignAsset.ts
  19. 2 1
      sdk/js/src/nft_bridge/getIsWrappedAsset.ts
  20. 2 3
      sdk/js/src/nft_bridge/getOriginalAsset.ts
  21. 4 5
      sdk/js/src/nft_bridge/redeem.ts
  22. 98 95
      sdk/js/src/nft_bridge/transfer.ts
  23. 34 0
      sdk/js/src/rpc/getSignedVAAWithRetry.ts
  24. 2 1
      sdk/js/src/solana/getBridgeFeeIx.ts
  25. 4 3
      sdk/js/src/solana/postVaa.ts
  26. 38 0
      sdk/js/src/solana/wasm.ts
  27. 35 0
      sdk/js/src/token_bridge/__tests__/consts.ts
  28. 393 0
      sdk/js/src/token_bridge/__tests__/integration.ts
  29. 4 4
      sdk/js/src/token_bridge/attest.ts
  30. 4 4
      sdk/js/src/token_bridge/createWrapped.ts
  31. 2 1
      sdk/js/src/token_bridge/getForeignAsset.ts
  32. 2 1
      sdk/js/src/token_bridge/getIsWrappedAsset.ts
  33. 3 3
      sdk/js/src/token_bridge/getOriginalAsset.ts
  34. 5 6
      sdk/js/src/token_bridge/redeem.ts
  35. 4 4
      sdk/js/src/token_bridge/transfer.ts
  36. 8 0
      solana/Dockerfile.wasm

+ 12 - 0
sdk/js/CHANGELOG.md

@@ -1,5 +1,17 @@
 # Changelog
 
+## 0.0.9
+
+### Added
+
+Integration tests
+
+NodeJS target wasm
+
+Ability to update attestations on EVM chains & Terra.
+
+nativeToHexString utility function for converting native addresses into VAA hex format.
+
 ## 0.0.8
 
 ### Added

+ 7 - 0
sdk/js/jestconfig.json

@@ -0,0 +1,7 @@
+{
+  "transform": {
+    "^.+\\.(t|j)sx?$": "ts-jest"
+  },
+  "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
+  "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
+}

文件差異過大導致無法顯示
+ 1025 - 589
sdk/js/package-lock.json


+ 6 - 2
sdk/js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@certusone/wormhole-sdk",
-  "version": "0.0.8",
+  "version": "0.0.9",
   "description": "SDK for interacting with Wormhole",
   "homepage": "https://wormholenetwork.com",
   "main": "lib/index.js",
@@ -15,7 +15,7 @@
     "build-deps": "npm run build-abis && npm run build-contracts",
     "build-lib": "tsc && node scripts/copyEthersTypes.js && node scripts/copyWasm.js",
     "build-all": "npm run build-deps && npm run build-lib",
-    "test": "echo \"Error: no test specified\" && exit 1",
+    "test": "jest --config jestconfig.json --verbose",
     "build": "npm run build-all",
     "format": "echo \"disabled: prettier --write \"src/**/*.ts\"\"",
     "lint": "tslint -p tsconfig.json",
@@ -38,14 +38,18 @@
   "author": "certusone",
   "license": "Apache-2.0",
   "devDependencies": {
+    "@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
     "@openzeppelin/contracts": "^4.2.0",
     "@typechain/ethers-v5": "^7.0.1",
+    "@types/jest": "^27.0.2",
     "@types/long": "^4.0.1",
     "@types/node": "^16.6.1",
     "@types/react": "^17.0.19",
     "copy-dir": "^1.3.0",
     "ethers": "^5.4.4",
+    "jest": "^27.3.1",
     "prettier": "^2.3.2",
+    "ts-jest": "^27.0.7",
     "tslint": "^6.1.3",
     "tslint-config-prettier": "^1.18.0",
     "typescript": "^4.3.5"

+ 2 - 1
sdk/js/src/bridge/getClaimAddress.ts

@@ -1,9 +1,10 @@
 import { PublicKey } from "@solana/web3.js";
+import { importCoreWasm } from "../solana/wasm";
 
 export async function getClaimAddressSolana(
   programAddress: string,
   signedVAA: Uint8Array
 ) {
-  const { claim_address } = await import("../solana/core/bridge");
+  const { claim_address } = await importCoreWasm();
   return new PublicKey(claim_address(programAddress, signedVAA));
 }

+ 2 - 1
sdk/js/src/bridge/getEmitterAddress.ts

@@ -1,6 +1,7 @@
 import { PublicKey } from "@solana/web3.js";
 import { bech32 } from "bech32";
 import { arrayify, BytesLike, Hexable, zeroPad } from "ethers/lib/utils";
+import { importTokenWasm } from "../solana/wasm";
 
 export function getEmitterAddressEth(
   contractAddress: number | BytesLike | Hexable
@@ -9,7 +10,7 @@ export function getEmitterAddressEth(
 }
 
 export async function getEmitterAddressSolana(programAddress: string) {
-  const { emitter_address } = await import("../solana/token/token_bridge");
+  const { emitter_address } = await importTokenWasm();
   return Buffer.from(
     zeroPad(new PublicKey(emitter_address(programAddress)).toBytes(), 32)
   ).toString("hex");

+ 2 - 3
sdk/js/src/migration/addLiquidity.ts

@@ -1,6 +1,7 @@
 import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
 import { Connection, PublicKey, Transaction } from "@solana/web3.js";
 import { ixFromRust } from "../solana";
+import { importMigrationWasm } from "../solana/wasm";
 
 export default async function addLiquidity(
   connection: Connection,
@@ -12,9 +13,7 @@ export default async function addLiquidity(
   lp_share_token_account: string,
   amount: BigInt
 ) {
-  const { authority_address, add_liquidity } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { authority_address, add_liquidity } = await importMigrationWasm();
   const approvalIx = Token.createApproveInstruction(
     TOKEN_PROGRAM_ID,
     new PublicKey(liquidity_token_account),

+ 3 - 3
sdk/js/src/migration/authorityAddress.ts

@@ -1,6 +1,6 @@
+import { importMigrationWasm } from "../solana/wasm";
+
 export default async function authorityAddress(program_id: string) {
-  const { authority_address } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { authority_address } = await importMigrationWasm();
   return authority_address(program_id);
 }

+ 2 - 3
sdk/js/src/migration/claimShares.ts

@@ -1,6 +1,7 @@
 import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
 import { Connection, PublicKey, Transaction } from "@solana/web3.js";
 import { ixFromRust } from "../solana";
+import { importMigrationWasm } from "../solana/wasm";
 
 export default async function claimShares(
   connection: Connection,
@@ -12,9 +13,7 @@ export default async function claimShares(
   lp_share_token_account: string,
   amount: BigInt
 ) {
-  const { authority_address, claim_shares } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { authority_address, claim_shares } = await importMigrationWasm();
   const approvalIx = Token.createApproveInstruction(
     TOKEN_PROGRAM_ID,
     new PublicKey(lp_share_token_account),

+ 2 - 3
sdk/js/src/migration/createPool.ts

@@ -1,5 +1,6 @@
 import { Connection, PublicKey, Transaction } from "@solana/web3.js";
 import { ixFromRust } from "../solana";
+import { importMigrationWasm } from "../solana/wasm";
 
 export default async function createPool(
   connection: Connection,
@@ -9,9 +10,7 @@ export default async function createPool(
   from_mint: string,
   to_mint: string
 ) {
-  const { create_pool } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { create_pool } = await importMigrationWasm();
   const ix = ixFromRust(create_pool(program_id, payer, from_mint, to_mint));
   const transaction = new Transaction().add(ix);
   const { blockhash } = await connection.getRecentBlockhash();

+ 3 - 3
sdk/js/src/migration/fromCustodyAddress.ts

@@ -1,9 +1,9 @@
+import { importMigrationWasm } from "../solana/wasm";
+
 export default async function fromCustodyAddress(
   program_id: string,
   pool: string
 ) {
-  const { from_custody_address } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { from_custody_address } = await importMigrationWasm();
   return from_custody_address(program_id, pool);
 }

+ 2 - 3
sdk/js/src/migration/migrateTokens.ts

@@ -1,6 +1,7 @@
 import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
 import { Connection, PublicKey, Transaction } from "@solana/web3.js";
 import { ixFromRust } from "../solana";
+import { importMigrationWasm } from "../solana/wasm";
 
 export default async function migrateTokens(
   connection: Connection,
@@ -12,9 +13,7 @@ export default async function migrateTokens(
   output_token_account: string,
   amount: BigInt
 ) {
-  const { authority_address, migrate_tokens } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { authority_address, migrate_tokens } = await importMigrationWasm();
   const approvalIx = Token.createApproveInstruction(
     TOKEN_PROGRAM_ID,
     new PublicKey(input_token_account),

+ 3 - 1
sdk/js/src/migration/parsePool.ts

@@ -1,4 +1,6 @@
+import { importMigrationWasm } from "../solana/wasm";
+
 export default async function parsePool(data: Uint8Array) {
-  const { parse_pool } = await import("../solana/migration/wormhole_migration");
+  const { parse_pool } = await importMigrationWasm();
   return parse_pool(data);
 }

+ 3 - 3
sdk/js/src/migration/poolAddress.ts

@@ -1,10 +1,10 @@
+import { importMigrationWasm } from "../solana/wasm";
+
 export default async function poolAddress(
   program_id: string,
   from_mint: string,
   to_mint: string
 ) {
-  const { pool_address } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { pool_address } = await importMigrationWasm();
   return pool_address(program_id, from_mint, to_mint);
 }

+ 2 - 3
sdk/js/src/migration/removeLiquidity.ts

@@ -1,6 +1,7 @@
 import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
 import { Connection, PublicKey, Transaction } from "@solana/web3.js";
 import { ixFromRust } from "../solana";
+import { importMigrationWasm } from "../solana/wasm";
 
 export default async function removeLiquidity(
   connection: Connection,
@@ -12,9 +13,7 @@ export default async function removeLiquidity(
   lp_share_token_account: string,
   amount: BigInt
 ) {
-  const { authority_address, remove_liquidity } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { authority_address, remove_liquidity } = await importMigrationWasm();
   const approvalIx = Token.createApproveInstruction(
     TOKEN_PROGRAM_ID,
     new PublicKey(lp_share_token_account),

+ 3 - 3
sdk/js/src/migration/shareMintAddress.ts

@@ -1,9 +1,9 @@
+import { importMigrationWasm } from "../solana/wasm";
+
 export default async function shareMintAddress(
   program_id: string,
   pool: string
 ) {
-  const { share_mint_address } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { share_mint_address } = await importMigrationWasm();
   return share_mint_address(program_id, pool);
 }

+ 3 - 3
sdk/js/src/migration/toCustodyAddress.ts

@@ -1,9 +1,9 @@
+import { importMigrationWasm } from "../solana/wasm";
+
 export default async function toCustodyAddress(
   program_id: string,
   pool: string
 ) {
-  const { to_custody_address } = await import(
-    "../solana/migration/wormhole_migration"
-  );
+  const { to_custody_address } = await importMigrationWasm();
   return to_custody_address(program_id, pool);
 }

+ 2 - 1
sdk/js/src/nft_bridge/getForeignAsset.ts

@@ -2,6 +2,7 @@ import { PublicKey } from "@solana/web3.js";
 import { ethers } from "ethers";
 import { CHAIN_ID_SOLANA } from "..";
 import { NFTBridge__factory } from "../ethers-contracts";
+import { importNftWasm } from "../solana/wasm";
 import { ChainId } from "../utils";
 
 /**
@@ -47,7 +48,7 @@ export async function getForeignAssetSol(
   originAsset: Uint8Array,
   tokenId: Uint8Array
 ) {
-  const { wrapped_address } = await import("../solana/nft/nft_bridge");
+  const { wrapped_address } = await importNftWasm();
   const wrappedAddress = wrapped_address(
     tokenBridgeAddress,
     originAsset,

+ 2 - 1
sdk/js/src/nft_bridge/getIsWrappedAsset.ts

@@ -1,6 +1,7 @@
 import { Connection, PublicKey } from "@solana/web3.js";
 import { ethers } from "ethers";
 import { Bridge__factory } from "../ethers-contracts";
+import { importNftWasm } from "../solana/wasm";
 
 /**
  * Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
@@ -32,7 +33,7 @@ export async function getIsWrappedAssetSol(
   mintAddress: string
 ) {
   if (!mintAddress) return false;
-  const { wrapped_meta_address } = await import("../solana/nft/nft_bridge");
+  const { wrapped_meta_address } = await importNftWasm();
   const wrappedMetaAddress = wrapped_meta_address(
     tokenBridgeAddress,
     new PublicKey(mintAddress).toBytes()

+ 2 - 3
sdk/js/src/nft_bridge/getOriginalAsset.ts

@@ -2,6 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
 import { BigNumber, ethers } from "ethers";
 import { arrayify, zeroPad } from "ethers/lib/utils";
 import { TokenImplementation__factory } from "../ethers-contracts";
+import { importNftWasm } from "../solana/wasm";
 import { ChainId, CHAIN_ID_SOLANA } from "../utils";
 import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
 
@@ -70,9 +71,7 @@ export async function getOriginalAssetSol(
 ): Promise<WormholeWrappedNFTInfo> {
   if (mintAddress) {
     // TODO: share some of this with getIsWrappedAssetSol, like a getWrappedMetaAccountAddress or something
-    const { parse_wrapped_meta, wrapped_meta_address } = await import(
-      "../solana/nft/nft_bridge"
-    );
+    const { parse_wrapped_meta, wrapped_meta_address } = await importNftWasm();
     const wrappedMetaAddress = wrapped_meta_address(
       tokenBridgeAddress,
       new PublicKey(mintAddress).toBytes()

+ 4 - 5
sdk/js/src/nft_bridge/redeem.ts

@@ -3,6 +3,7 @@ import { ethers } from "ethers";
 import { CHAIN_ID_SOLANA } from "..";
 import { Bridge__factory } from "../ethers-contracts";
 import { ixFromRust } from "../solana";
+import { importCoreWasm, importNftWasm } from "../solana/wasm";
 
 export async function redeemOnEth(
   tokenBridgeAddress: string,
@@ -16,7 +17,7 @@ export async function redeemOnEth(
 }
 
 export async function isNFTVAASolanaNative(signedVAA: Uint8Array) {
-  const { parse_vaa } = await import("../solana/core/bridge");
+  const { parse_vaa } = await importCoreWasm();
   const parsedVAA = parse_vaa(signedVAA);
   const isSolanaNative =
     Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(33) ===
@@ -33,7 +34,7 @@ export async function redeemOnSolana(
 ) {
   const isSolanaNative = await isNFTVAASolanaNative(signedVAA);
   const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
-    await import("../solana/nft/nft_bridge");
+    await importNftWasm();
   const ixs = [];
   if (isSolanaNative) {
     ixs.push(
@@ -74,9 +75,7 @@ export async function createMetaOnSolana(
   payerAddress: string,
   signedVAA: Uint8Array
 ) {
-  const { complete_transfer_wrapped_meta_ix } = await import(
-    "../solana/nft/nft_bridge"
-  );
+  const { complete_transfer_wrapped_meta_ix } = await importNftWasm();
   const ix = ixFromRust(
     complete_transfer_wrapped_meta_ix(
       tokenBridgeAddress,

+ 98 - 95
sdk/js/src/nft_bridge/transfer.ts

@@ -1,106 +1,109 @@
-import {Token, TOKEN_PROGRAM_ID} from "@solana/spl-token";
-import {Connection, Keypair, PublicKey, Transaction} from "@solana/web3.js";
-import {ethers} from "ethers";
+import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
+import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
+import { ethers } from "ethers";
 import {
-    NFTBridge__factory,
-    NFTImplementation__factory,
+  NFTBridge__factory,
+  NFTImplementation__factory,
 } from "../ethers-contracts";
-import {getBridgeFeeIx, ixFromRust} from "../solana";
-import {ChainId, CHAIN_ID_SOLANA, createNonce} from "../utils";
+import { getBridgeFeeIx, ixFromRust } from "../solana";
+import { importNftWasm } from "../solana/wasm";
+import { ChainId, CHAIN_ID_SOLANA, createNonce } from "../utils";
 
 export async function transferFromEth(
-    tokenBridgeAddress: string,
-    signer: ethers.Signer,
-    tokenAddress: string,
-    tokenID: ethers.BigNumberish,
-    recipientChain: ChainId,
-    recipientAddress: Uint8Array
+  tokenBridgeAddress: string,
+  signer: ethers.Signer,
+  tokenAddress: string,
+  tokenID: ethers.BigNumberish,
+  recipientChain: ChainId,
+  recipientAddress: Uint8Array
 ) {
-    //TODO: should we check if token attestation exists on the target chain
-    const token = NFTImplementation__factory.connect(tokenAddress, signer);
-    await (await token.approve(tokenBridgeAddress, tokenID)).wait();
-    const bridge = NFTBridge__factory.connect(tokenBridgeAddress, signer);
-    const v = await bridge.transferNFT(
-        tokenAddress,
-        tokenID,
-        recipientChain,
-        recipientAddress,
-        createNonce()
-    );
-    const receipt = await v.wait();
-    return receipt;
+  //TODO: should we check if token attestation exists on the target chain
+  const token = NFTImplementation__factory.connect(tokenAddress, signer);
+  await (await token.approve(tokenBridgeAddress, tokenID)).wait();
+  const bridge = NFTBridge__factory.connect(tokenBridgeAddress, signer);
+  const v = await bridge.transferNFT(
+    tokenAddress,
+    tokenID,
+    recipientChain,
+    recipientAddress,
+    createNonce()
+  );
+  const receipt = await v.wait();
+  return receipt;
 }
 
 export async function transferFromSolana(
-    connection: Connection,
-    bridgeAddress: string,
-    tokenBridgeAddress: string,
-    payerAddress: string,
-    fromAddress: string,
-    mintAddress: string,
-    targetAddress: Uint8Array,
-    targetChain: ChainId,
-    originAddress?: Uint8Array,
-    originChain?: ChainId,
-    originTokenId?: Uint8Array
+  connection: Connection,
+  bridgeAddress: string,
+  tokenBridgeAddress: string,
+  payerAddress: string,
+  fromAddress: string,
+  mintAddress: string,
+  targetAddress: Uint8Array,
+  targetChain: ChainId,
+  originAddress?: Uint8Array,
+  originChain?: ChainId,
+  originTokenId?: Uint8Array
 ) {
-    const nonce = createNonce().readUInt32LE(0);
-    const transferIx = await getBridgeFeeIx(
-        connection,
-        bridgeAddress,
-        payerAddress
-    );
-    const {
-        transfer_native_ix,
-        transfer_wrapped_ix,
-        approval_authority_address,
-    } = await import("../solana/nft/nft_bridge");
-    const approvalIx = Token.createApproveInstruction(
-        TOKEN_PROGRAM_ID,
-        new PublicKey(fromAddress),
-        new PublicKey(approval_authority_address(tokenBridgeAddress)),
-        new PublicKey(payerAddress),
-        [],
-        Number(1)
-    );
-    let messageKey = Keypair.generate();
-    const isSolanaNative =
-        originChain === undefined || originChain === CHAIN_ID_SOLANA;
-    if (!isSolanaNative && !originAddress && !originTokenId) {
-        throw new Error("originAddress and originTokenId are required when specifying originChain");
-    }
-    const ix = ixFromRust(
-        isSolanaNative
-            ? transfer_native_ix(
-                tokenBridgeAddress,
-                bridgeAddress,
-                payerAddress,
-                messageKey.publicKey.toString(),
-                fromAddress,
-                mintAddress,
-                nonce,
-                targetAddress,
-                targetChain
-            )
-            : transfer_wrapped_ix(
-                tokenBridgeAddress,
-                bridgeAddress,
-                payerAddress,
-                messageKey.publicKey.toString(),
-                fromAddress,
-                payerAddress,
-                originChain as number, // checked by isSolanaNative
-                originAddress as Uint8Array, // checked by throw
-                originTokenId as Uint8Array, // checked by throw
-                nonce,
-                targetAddress,
-                targetChain
-            )
+  const nonce = createNonce().readUInt32LE(0);
+  const transferIx = await getBridgeFeeIx(
+    connection,
+    bridgeAddress,
+    payerAddress
+  );
+  const {
+    transfer_native_ix,
+    transfer_wrapped_ix,
+    approval_authority_address,
+  } = await importNftWasm();
+  const approvalIx = Token.createApproveInstruction(
+    TOKEN_PROGRAM_ID,
+    new PublicKey(fromAddress),
+    new PublicKey(approval_authority_address(tokenBridgeAddress)),
+    new PublicKey(payerAddress),
+    [],
+    Number(1)
+  );
+  let messageKey = Keypair.generate();
+  const isSolanaNative =
+    originChain === undefined || originChain === CHAIN_ID_SOLANA;
+  if (!isSolanaNative && !originAddress && !originTokenId) {
+    throw new Error(
+      "originAddress and originTokenId are required when specifying originChain"
     );
-    const transaction = new Transaction().add(transferIx, approvalIx, ix);
-    const {blockhash} = await connection.getRecentBlockhash();
-    transaction.recentBlockhash = blockhash;
-    transaction.feePayer = new PublicKey(payerAddress);
-    transaction.partialSign(messageKey);
-    return transaction;
+  }
+  const ix = ixFromRust(
+    isSolanaNative
+      ? transfer_native_ix(
+          tokenBridgeAddress,
+          bridgeAddress,
+          payerAddress,
+          messageKey.publicKey.toString(),
+          fromAddress,
+          mintAddress,
+          nonce,
+          targetAddress,
+          targetChain
+        )
+      : transfer_wrapped_ix(
+          tokenBridgeAddress,
+          bridgeAddress,
+          payerAddress,
+          messageKey.publicKey.toString(),
+          fromAddress,
+          payerAddress,
+          originChain as number, // checked by isSolanaNative
+          originAddress as Uint8Array, // checked by throw
+          originTokenId as Uint8Array, // checked by throw
+          nonce,
+          targetAddress,
+          targetChain
+        )
+  );
+  const transaction = new Transaction().add(transferIx, approvalIx, ix);
+  const { blockhash } = await connection.getRecentBlockhash();
+  transaction.recentBlockhash = blockhash;
+  transaction.feePayer = new PublicKey(payerAddress);
+  transaction.partialSign(messageKey);
+  return transaction;
 }

+ 34 - 0
sdk/js/src/rpc/getSignedVAAWithRetry.ts

@@ -0,0 +1,34 @@
+import { ChainId, getSignedVAA } from "..";
+
+export default async function getSignedVAAWithRetry(
+  hosts: string[],
+  emitterChain: ChainId,
+  emitterAddress: string,
+  sequence: string,
+  extraGrpcOpts = {},
+  retryTimeout = 1000,
+  retryAttempts?: number
+) {
+  let currentWormholeRpcHost = -1;
+  const getNextRpcHost = () => ++currentWormholeRpcHost % hosts.length;
+  let result;
+  let attempts = 0;
+  while (!result) {
+    attempts++;
+    await new Promise((resolve) => setTimeout(resolve, retryTimeout));
+    try {
+      result = await getSignedVAA(
+        hosts[getNextRpcHost()],
+        emitterChain,
+        emitterAddress,
+        sequence,
+        extraGrpcOpts
+      );
+    } catch (e) {
+      if (retryAttempts !== undefined && attempts > retryAttempts) {
+        throw e;
+      }
+    }
+  }
+  return result;
+}

+ 2 - 1
sdk/js/src/solana/getBridgeFeeIx.ts

@@ -1,11 +1,12 @@
 import { Connection, PublicKey, SystemProgram } from "@solana/web3.js";
+import { importCoreWasm } from "./wasm";
 
 export async function getBridgeFeeIx(
   connection: Connection,
   bridgeAddress: string,
   payerAddress: string
 ) {
-  const bridge = await import("./core/bridge");
+  const bridge = await importCoreWasm();
   const feeAccount = await bridge.fee_collector_address(bridgeAddress);
   const bridgeStatePK = new PublicKey(bridge.state_address(bridgeAddress));
   const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK);

+ 4 - 3
sdk/js/src/solana/postVaa.ts

@@ -6,11 +6,12 @@ import {
   TransactionInstruction,
 } from "@solana/web3.js";
 import { ixFromRust } from "./rust";
+import { importCoreWasm } from "./wasm";
 
 // is there a better pattern for this?
 export async function postVaa(
   connection: Connection,
-  signTransaction: (transaction: Transaction) => any,
+  signTransaction: (transaction: Transaction) => Promise<Transaction>,
   bridge_id: string,
   payer: string,
   vaa: Buffer
@@ -20,7 +21,7 @@ export async function postVaa(
     parse_guardian_set,
     verify_signatures_ix,
     post_vaa_ix,
-  } = await import("./core/bridge");
+  } = await importCoreWasm();
   let bridge_state = await getBridgeState(connection, bridge_id);
   let guardian_addr = new PublicKey(
     guardian_set_address(bridge_id, bridge_state.guardian_set_index)
@@ -74,7 +75,7 @@ async function getBridgeState(
   connection: Connection,
   bridge_id: string
 ): Promise<BridgeState> {
-  const { parse_state, state_address } = await import("./core/bridge");
+  const { parse_state, state_address } = await importCoreWasm();
   let bridge_state = new PublicKey(state_address(bridge_id));
   let acc = await connection.getAccountInfo(bridge_state);
   if (acc?.data === undefined) {

+ 38 - 0
sdk/js/src/solana/wasm.ts

@@ -0,0 +1,38 @@
+const coreWasms = {
+  bundler: async () => await import("./core/bridge"),
+  node: async () => await import("./core-node/bridge"),
+};
+const migrationWasms = {
+  bundler: async () => await import("./migration/wormhole_migration"),
+  node: async () => await import("./migration-node/wormhole_migration"),
+};
+const nftWasms = {
+  bundler: async () => await import("./nft/nft_bridge"),
+  node: async () => await import("./nft-node/nft_bridge"),
+};
+const tokenWasms = {
+  bundler: async () => await import("./token/token_bridge"),
+  node: async () => await import("./token-node/token_bridge"),
+};
+let importDefaultCoreWasm = coreWasms.bundler;
+let importDefaultMigrationWasm = migrationWasms.bundler;
+let importDefaultNftWasm = nftWasms.bundler;
+let importDefaultTokenWasm = tokenWasms.bundler;
+export function setDefaultWasm(type: "bundler" | "node") {
+  importDefaultCoreWasm = coreWasms[type];
+  importDefaultMigrationWasm = migrationWasms[type];
+  importDefaultNftWasm = nftWasms[type];
+  importDefaultTokenWasm = tokenWasms[type];
+}
+export async function importCoreWasm() {
+  return await importDefaultCoreWasm();
+}
+export async function importMigrationWasm() {
+  return await importDefaultMigrationWasm();
+}
+export async function importNftWasm() {
+  return await importDefaultNftWasm();
+}
+export async function importTokenWasm() {
+  return await importDefaultTokenWasm();
+}

+ 35 - 0
sdk/js/src/token_bridge/__tests__/consts.ts

@@ -0,0 +1,35 @@
+import { describe, expect, it } from "@jest/globals";
+import { Connection, PublicKey } from "@solana/web3.js";
+
+// see devnet.md
+export const ETH_NODE_URL = "ws://localhost:8545";
+export const ETH_PRIVATE_KEY =
+  "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d";
+export const ETH_CORE_BRIDGE_ADDRESS =
+  "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550";
+export const ETH_TOKEN_BRIDGE_ADDRESS =
+  "0x0290FB167208Af455bB137780163b7B7a9a10C16";
+export const SOLANA_HOST = "http://localhost:8899";
+export const SOLANA_PRIVATE_KEY = new Uint8Array([
+  14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89,
+  84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65,
+  8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47,
+  44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141,
+]);
+export const SOLANA_CORE_BRIDGE_ADDRESS =
+  "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
+export const SOLANA_TOKEN_BRIDGE_ADDRESS =
+  "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE";
+export const TEST_ERC20 = "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A";
+export const TEST_SOLANA_TOKEN = "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ";
+export const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
+
+describe("consts should exist", () => {
+  it("has Solana test token", () => {
+    expect.assertions(1);
+    const connection = new Connection(SOLANA_HOST, "confirmed");
+    return expect(
+      connection.getAccountInfo(new PublicKey(TEST_SOLANA_TOKEN))
+    ).resolves.toBeTruthy();
+  });
+});

+ 393 - 0
sdk/js/src/token_bridge/__tests__/integration.ts

@@ -0,0 +1,393 @@
+import { parseUnits } from "@ethersproject/units";
+import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
+import { describe, jest, test } from "@jest/globals";
+import {
+  ASSOCIATED_TOKEN_PROGRAM_ID,
+  Token,
+  TOKEN_PROGRAM_ID,
+} from "@solana/spl-token";
+import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
+import { ethers } from "ethers";
+import {
+  approveEth,
+  attestFromEth,
+  attestFromSolana,
+  CHAIN_ID_ETH,
+  CHAIN_ID_SOLANA,
+  createWrappedOnEth,
+  createWrappedOnSolana,
+  getEmitterAddressEth,
+  getEmitterAddressSolana,
+  getForeignAssetSolana,
+  hexToUint8Array,
+  nativeToHexString,
+  parseSequenceFromLogEth,
+  parseSequenceFromLogSolana,
+  postVaaSolana,
+  redeemOnEth,
+  redeemOnSolana,
+  transferFromEth,
+  transferFromSolana,
+} from "../..";
+import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
+import { setDefaultWasm } from "../../solana/wasm";
+import {
+  ETH_CORE_BRIDGE_ADDRESS,
+  ETH_NODE_URL,
+  ETH_PRIVATE_KEY,
+  ETH_TOKEN_BRIDGE_ADDRESS,
+  SOLANA_CORE_BRIDGE_ADDRESS,
+  SOLANA_HOST,
+  SOLANA_PRIVATE_KEY,
+  SOLANA_TOKEN_BRIDGE_ADDRESS,
+  TEST_ERC20,
+  TEST_SOLANA_TOKEN,
+  WORMHOLE_RPC_HOSTS,
+} from "./consts";
+
+setDefaultWasm("node");
+
+jest.setTimeout(60000);
+
+// TODO: setup keypair and provider/signer before, destroy provider after
+// TODO: make the repeatable (can't attest an already attested token)
+// TODO: add Terra
+
+describe("Integration Tests", () => {
+  describe("Ethereum to Solana", () => {
+    test("Attest Ethereum ERC-20 to Solana", (done) => {
+      (async () => {
+        try {
+          // create a signer for Eth
+          const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
+          const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
+          // attest the test token
+          const receipt = await attestFromEth(
+            ETH_TOKEN_BRIDGE_ADDRESS,
+            signer,
+            TEST_ERC20
+          );
+          // get the sequence from the logs (needed to fetch the vaa)
+          const sequence = parseSequenceFromLogEth(
+            receipt,
+            ETH_CORE_BRIDGE_ADDRESS
+          );
+          const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
+          // poll until the guardian(s) witness and sign the vaa
+          const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
+            WORMHOLE_RPC_HOSTS,
+            CHAIN_ID_ETH,
+            emitterAddress,
+            sequence,
+            {
+              transport: NodeHttpTransport(),
+            }
+          );
+          // create a keypair for Solana
+          const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
+          const payerAddress = keypair.publicKey.toString();
+          // post vaa to Solana
+          const connection = new Connection(SOLANA_HOST, "confirmed");
+          await postVaaSolana(
+            connection,
+            async (transaction) => {
+              transaction.partialSign(keypair);
+              return transaction;
+            },
+            SOLANA_CORE_BRIDGE_ADDRESS,
+            payerAddress,
+            Buffer.from(signedVAA)
+          );
+          // create wormhole wrapped token (mint and metadata) on solana
+          const transaction = await createWrappedOnSolana(
+            connection,
+            SOLANA_CORE_BRIDGE_ADDRESS,
+            SOLANA_TOKEN_BRIDGE_ADDRESS,
+            payerAddress,
+            signedVAA
+          );
+          // sign, send, and confirm transaction
+          try {
+            transaction.partialSign(keypair);
+            const txid = await connection.sendRawTransaction(
+              transaction.serialize()
+            );
+            await connection.confirmTransaction(txid);
+          } catch (e) {
+            // this could fail because the token is already attested (in an unclean env)
+          }
+          provider.destroy();
+          done();
+        } catch (e) {
+          console.error(e);
+          done(
+            "An error occurred while trying to attest from Ethereum to Solana"
+          );
+        }
+      })();
+    });
+    // TODO: it is attested
+    test("Send Ethereum ERC-20 to Solana", (done) => {
+      (async () => {
+        try {
+          // create a keypair for Solana
+          const connection = new Connection(SOLANA_HOST, "confirmed");
+          const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
+          const payerAddress = keypair.publicKey.toString();
+          // determine destination address - an associated token account
+          const solanaMintKey = new PublicKey(
+            (await getForeignAssetSolana(
+              connection,
+              SOLANA_TOKEN_BRIDGE_ADDRESS,
+              CHAIN_ID_ETH,
+              hexToUint8Array(nativeToHexString(TEST_ERC20, CHAIN_ID_ETH) || "")
+            )) || ""
+          );
+          const recipient = await Token.getAssociatedTokenAddress(
+            ASSOCIATED_TOKEN_PROGRAM_ID,
+            TOKEN_PROGRAM_ID,
+            solanaMintKey,
+            keypair.publicKey
+          );
+          // create the associated token account if it doesn't exist
+          const associatedAddressInfo = await connection.getAccountInfo(
+            recipient
+          );
+          if (!associatedAddressInfo) {
+            const transaction = new Transaction().add(
+              await Token.createAssociatedTokenAccountInstruction(
+                ASSOCIATED_TOKEN_PROGRAM_ID,
+                TOKEN_PROGRAM_ID,
+                solanaMintKey,
+                recipient,
+                keypair.publicKey, // owner
+                keypair.publicKey // payer
+              )
+            );
+            const { blockhash } = await connection.getRecentBlockhash();
+            transaction.recentBlockhash = blockhash;
+            transaction.feePayer = keypair.publicKey;
+            // sign, send, and confirm transaction
+            transaction.partialSign(keypair);
+            const txid = await connection.sendRawTransaction(
+              transaction.serialize()
+            );
+            await connection.confirmTransaction(txid);
+          }
+          // create a signer for Eth
+          const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
+          const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
+          const amount = parseUnits("1", 18);
+          // approve the bridge to spend tokens
+          await approveEth(
+            ETH_TOKEN_BRIDGE_ADDRESS,
+            TEST_ERC20,
+            signer,
+            amount
+          );
+          // transfer tokens
+          const receipt = await transferFromEth(
+            ETH_TOKEN_BRIDGE_ADDRESS,
+            signer,
+            TEST_ERC20,
+            amount,
+            CHAIN_ID_SOLANA,
+            hexToUint8Array(
+              nativeToHexString(recipient.toString(), CHAIN_ID_SOLANA) || ""
+            )
+          );
+          // get the sequence from the logs (needed to fetch the vaa)
+          const sequence = parseSequenceFromLogEth(
+            receipt,
+            ETH_CORE_BRIDGE_ADDRESS
+          );
+          const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
+          // poll until the guardian(s) witness and sign the vaa
+          const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
+            WORMHOLE_RPC_HOSTS,
+            CHAIN_ID_ETH,
+            emitterAddress,
+            sequence,
+            {
+              transport: NodeHttpTransport(),
+            }
+          );
+          // post vaa to Solana
+          await postVaaSolana(
+            connection,
+            async (transaction) => {
+              transaction.partialSign(keypair);
+              return transaction;
+            },
+            SOLANA_CORE_BRIDGE_ADDRESS,
+            payerAddress,
+            Buffer.from(signedVAA)
+          );
+          // redeem tokens on solana
+          const transaction = await redeemOnSolana(
+            connection,
+            SOLANA_CORE_BRIDGE_ADDRESS,
+            SOLANA_TOKEN_BRIDGE_ADDRESS,
+            payerAddress,
+            signedVAA
+          );
+          // sign, send, and confirm transaction
+          transaction.partialSign(keypair);
+          const txid = await connection.sendRawTransaction(
+            transaction.serialize()
+          );
+          await connection.confirmTransaction(txid);
+          provider.destroy();
+          done();
+        } catch (e) {
+          console.error(e);
+          done(
+            "An error occurred while trying to send from Ethereum to Solana"
+          );
+        }
+      })();
+    });
+    // TODO: it has increased balance
+  });
+  describe("Solana to Ethereum", () => {
+    test("Attest Solana SPL to Ethereum", (done) => {
+      (async () => {
+        try {
+          // create a keypair for Solana
+          const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
+          const payerAddress = keypair.publicKey.toString();
+          // attest the test token
+          const connection = new Connection(SOLANA_HOST, "confirmed");
+          const transaction = await attestFromSolana(
+            connection,
+            SOLANA_CORE_BRIDGE_ADDRESS,
+            SOLANA_TOKEN_BRIDGE_ADDRESS,
+            payerAddress,
+            TEST_SOLANA_TOKEN
+          );
+          // sign, send, and confirm transaction
+          transaction.partialSign(keypair);
+          const txid = await connection.sendRawTransaction(
+            transaction.serialize()
+          );
+          await connection.confirmTransaction(txid);
+          const info = await connection.getTransaction(txid);
+          if (!info) {
+            throw new Error(
+              "An error occurred while fetching the transaction info"
+            );
+          }
+          // get the sequence from the logs (needed to fetch the vaa)
+          const sequence = parseSequenceFromLogSolana(info);
+          const emitterAddress = await getEmitterAddressSolana(
+            SOLANA_TOKEN_BRIDGE_ADDRESS
+          );
+          // poll until the guardian(s) witness and sign the vaa
+          const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
+            WORMHOLE_RPC_HOSTS,
+            CHAIN_ID_SOLANA,
+            emitterAddress,
+            sequence,
+            {
+              transport: NodeHttpTransport(),
+            }
+          );
+          // create a signer for Eth
+          const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
+          const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
+          try {
+            await createWrappedOnEth(
+              ETH_TOKEN_BRIDGE_ADDRESS,
+              signer,
+              signedVAA
+            );
+          } catch (e) {
+            // this could fail because the token is already attested (in an unclean env)
+          }
+          provider.destroy();
+          done();
+        } catch (e) {
+          console.error(e);
+          done(
+            "An error occurred while trying to attest from Solana to Ethereum"
+          );
+        }
+      })();
+    });
+    // TODO: it is attested
+    test("Send Solana SPL to Ethereum", (done) => {
+      (async () => {
+        try {
+          // create a signer for Eth
+          const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
+          const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
+          const targetAddress = await signer.getAddress();
+          // create a keypair for Solana
+          const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
+          const payerAddress = keypair.publicKey.toString();
+          // find the associated token account
+          const fromAddress = (
+            await Token.getAssociatedTokenAddress(
+              ASSOCIATED_TOKEN_PROGRAM_ID,
+              TOKEN_PROGRAM_ID,
+              new PublicKey(TEST_SOLANA_TOKEN),
+              keypair.publicKey
+            )
+          ).toString();
+          // transfer the test token
+          const connection = new Connection(SOLANA_HOST, "confirmed");
+          const amount = parseUnits("1", 9).toBigInt();
+          const transaction = await transferFromSolana(
+            connection,
+            SOLANA_CORE_BRIDGE_ADDRESS,
+            SOLANA_TOKEN_BRIDGE_ADDRESS,
+            payerAddress,
+            fromAddress,
+            TEST_SOLANA_TOKEN,
+            amount,
+            hexToUint8Array(
+              nativeToHexString(targetAddress, CHAIN_ID_ETH) || ""
+            ),
+            CHAIN_ID_ETH
+          );
+          // sign, send, and confirm transaction
+          transaction.partialSign(keypair);
+          const txid = await connection.sendRawTransaction(
+            transaction.serialize()
+          );
+          await connection.confirmTransaction(txid);
+          const info = await connection.getTransaction(txid);
+          if (!info) {
+            throw new Error(
+              "An error occurred while fetching the transaction info"
+            );
+          }
+          // get the sequence from the logs (needed to fetch the vaa)
+          const sequence = parseSequenceFromLogSolana(info);
+          const emitterAddress = await getEmitterAddressSolana(
+            SOLANA_TOKEN_BRIDGE_ADDRESS
+          );
+          // poll until the guardian(s) witness and sign the vaa
+          const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
+            WORMHOLE_RPC_HOSTS,
+            CHAIN_ID_SOLANA,
+            emitterAddress,
+            sequence,
+            {
+              transport: NodeHttpTransport(),
+            }
+          );
+          await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
+          provider.destroy();
+          done();
+        } catch (e) {
+          console.error(e);
+          done(
+            "An error occurred while trying to attest from Solana to Ethereum"
+          );
+        }
+      })();
+    });
+    // TODO: it has increased balance
+  });
+});

+ 4 - 4
sdk/js/src/token_bridge/attest.ts

@@ -1,11 +1,11 @@
 import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
+import { MsgExecuteContract } from "@terra-money/terra.js";
 import { ethers } from "ethers";
+import { isNativeDenom } from "..";
 import { Bridge__factory } from "../ethers-contracts";
 import { getBridgeFeeIx, ixFromRust } from "../solana";
+import { importTokenWasm } from "../solana/wasm";
 import { createNonce } from "../utils/createNonce";
-import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
-import { MsgExecuteContract } from "@terra-money/terra.js";
-import { isNativeDenom } from "..";
 
 export async function attestFromEth(
   tokenBridgeAddress: string,
@@ -54,7 +54,7 @@ export async function attestFromSolana(
     bridgeAddress,
     payerAddress
   );
-  const { attest_ix } = await import("../solana/token/token_bridge");
+  const { attest_ix } = await importTokenWasm();
   const messageKey = Keypair.generate();
   const ix = ixFromRust(
     attest_ix(

+ 4 - 4
sdk/js/src/token_bridge/createWrapped.ts

@@ -1,10 +1,10 @@
 import { Connection, PublicKey, Transaction } from "@solana/web3.js";
+import { MsgExecuteContract } from "@terra-money/terra.js";
 import { ethers } from "ethers";
+import { fromUint8Array } from "js-base64";
 import { Bridge__factory } from "../ethers-contracts";
-import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
-import { MsgExecuteContract } from "@terra-money/terra.js";
 import { ixFromRust } from "../solana";
-import { fromUint8Array } from "js-base64";
+import { importTokenWasm } from "../solana/wasm";
 
 export async function createWrappedOnEth(
   tokenBridgeAddress: string,
@@ -36,7 +36,7 @@ export async function createWrappedOnSolana(
   payerAddress: string,
   signedVAA: Uint8Array
 ) {
-  const { create_wrapped_ix } = await import("../solana/token/token_bridge");
+  const { create_wrapped_ix } = await importTokenWasm();
   const ix = ixFromRust(
     create_wrapped_ix(
       tokenBridgeAddress,

+ 2 - 1
sdk/js/src/token_bridge/getForeignAsset.ts

@@ -4,6 +4,7 @@ import { Bridge__factory } from "../ethers-contracts";
 import { ChainId } from "../utils";
 import { LCDClient } from "@terra-money/terra.js";
 import { fromUint8Array } from "js-base64";
+import { importTokenWasm } from "../solana/wasm";
 
 /**
  * Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
@@ -63,7 +64,7 @@ export async function getForeignAssetSolana(
   originChain: ChainId,
   originAsset: Uint8Array
 ) {
-  const { wrapped_address } = await import("../solana/token/token_bridge");
+  const { wrapped_address } = await importTokenWasm();
   const wrappedAddress = wrapped_address(
     tokenBridgeAddress,
     originAsset,

+ 2 - 1
sdk/js/src/token_bridge/getIsWrappedAsset.ts

@@ -2,6 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
 import { ethers } from "ethers";
 import { Bridge__factory } from "../ethers-contracts";
 import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
+import { importTokenWasm } from "../solana/wasm";
 
 /**
  * Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
@@ -41,7 +42,7 @@ export async function getIsWrappedAssetSol(
   mintAddress: string
 ) {
   if (!mintAddress) return false;
-  const { wrapped_meta_address } = await import("../solana/token/token_bridge");
+  const { wrapped_meta_address } = await importTokenWasm();
   const wrappedMetaAddress = wrapped_meta_address(
     tokenBridgeAddress,
     new PublicKey(mintAddress).toBytes()

+ 3 - 3
sdk/js/src/token_bridge/getOriginalAsset.ts

@@ -3,6 +3,7 @@ import { LCDClient } from "@terra-money/terra.js";
 import { ethers } from "ethers";
 import { arrayify, zeroPad } from "ethers/lib/utils";
 import { TokenImplementation__factory } from "../ethers-contracts";
+import { importTokenWasm } from "../solana/wasm";
 import { buildNativeId, canonicalAddress, isNativeDenom } from "../terra";
 import { ChainId, CHAIN_ID_SOLANA, CHAIN_ID_TERRA } from "../utils";
 import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
@@ -101,9 +102,8 @@ export async function getOriginalAssetSol(
 ): Promise<WormholeWrappedInfo> {
   if (mintAddress) {
     // TODO: share some of this with getIsWrappedAssetSol, like a getWrappedMetaAccountAddress or something
-    const { parse_wrapped_meta, wrapped_meta_address } = await import(
-      "../solana/token/token_bridge"
-    );
+    const { parse_wrapped_meta, wrapped_meta_address } =
+      await importTokenWasm();
     const wrappedMetaAddress = wrapped_meta_address(
       tokenBridgeAddress,
       new PublicKey(mintAddress).toBytes()

+ 5 - 6
sdk/js/src/token_bridge/redeem.ts

@@ -11,6 +11,7 @@ import { ethers } from "ethers";
 import { fromUint8Array } from "js-base64";
 import { Bridge__factory } from "../ethers-contracts";
 import { ixFromRust } from "../solana";
+import { importCoreWasm, importTokenWasm } from "../solana/wasm";
 import {
   CHAIN_ID_SOLANA,
   WSOL_ADDRESS,
@@ -61,10 +62,8 @@ export async function redeemAndUnwrapOnSolana(
   payerAddress: string,
   signedVAA: Uint8Array
 ) {
-  const { parse_vaa } = await import("../solana/core/bridge");
-  const { complete_transfer_native_ix } = await import(
-    "../solana/token/token_bridge"
-  );
+  const { parse_vaa } = await importCoreWasm();
+  const { complete_transfer_native_ix } = await importTokenWasm();
   const parsedVAA = parse_vaa(signedVAA);
   const parsedPayload = parseTransferPayload(
     Buffer.from(new Uint8Array(parsedVAA.payload))
@@ -151,13 +150,13 @@ export async function redeemOnSolana(
   payerAddress: string,
   signedVAA: Uint8Array
 ) {
-  const { parse_vaa } = await import("../solana/core/bridge");
+  const { parse_vaa } = await importCoreWasm();
   const parsedVAA = parse_vaa(signedVAA);
   const isSolanaNative =
     Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(65) ===
     CHAIN_ID_SOLANA;
   const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
-    await import("../solana/token/token_bridge");
+    await importTokenWasm();
   const ixs = [];
   if (isSolanaNative) {
     ixs.push(

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

@@ -14,6 +14,7 @@ import {
   TokenImplementation__factory,
 } from "../ethers-contracts";
 import { getBridgeFeeIx, ixFromRust } from "../solana";
+import { importTokenWasm } from "../solana/wasm";
 import { ChainId, CHAIN_ID_SOLANA, createNonce, WSOL_ADDRESS } from "../utils";
 
 export async function getAllowanceEth(
@@ -202,9 +203,8 @@ export async function transferNativeSol(
   );
 
   //Normal approve & transfer instructions, except that the wSOL is sent from the ancillary account.
-  const { transfer_native_ix, approval_authority_address } = await import(
-    "../solana/token/token_bridge"
-  );
+  const { transfer_native_ix, approval_authority_address } =
+    await importTokenWasm();
   const nonce = createNonce().readUInt32LE(0);
   const fee = BigInt(0); // for now, this won't do anything, we may add later
   const transferIx = await getBridgeFeeIx(
@@ -286,7 +286,7 @@ export async function transferFromSolana(
     transfer_native_ix,
     transfer_wrapped_ix,
     approval_authority_address,
-  } = await import("../solana/token/token_bridge");
+  } = await importTokenWasm();
   const approvalIx = Token.createApproveInstruction(
     TOKEN_PROGRAM_ID,
     new PublicKey(fromAddress),

+ 8 - 0
solana/Dockerfile.wasm

@@ -42,6 +42,10 @@ RUN --mount=type=cache,target=/root/.cache \
 	--mount=type=cache,target=migration/target \
     cd migration && /usr/local/cargo/bin/wasm-pack build --target bundler -d bundler -- --features wasm
 
+RUN --mount=type=cache,target=/root/.cache \
+	--mount=type=cache,target=migration/target \
+    cd migration && /usr/local/cargo/bin/wasm-pack build --target nodejs -d nodejs -- --features wasm
+
 # Compile NFT Bridge
 RUN --mount=type=cache,target=/root/.cache \
 	--mount=type=cache,target=modules/nft_bridge/target \
@@ -73,6 +77,10 @@ COPY --from=build /usr/src/bridge/bridge/program/bundler explorer/wasm/core
 COPY --from=build /usr/src/bridge/modules/token_bridge/program/bundler explorer/wasm/token
 COPY --from=build /usr/src/bridge/modules/nft_bridge/program/bundler explorer/wasm/nft
 
+COPY --from=build /usr/src/bridge/bridge/program/nodejs sdk/js/src/solana/core-node
+COPY --from=build /usr/src/bridge/modules/token_bridge/program/nodejs sdk/js/src/solana/token-node
+COPY --from=build /usr/src/bridge/migration/nodejs sdk/js/src/solana/migration-node
+COPY --from=build /usr/src/bridge/modules/nft_bridge/program/nodejs sdk/js/src/solana/nft-node
 COPY --from=build /usr/src/bridge/bridge/program/nodejs clients/solana/pkg
 COPY --from=build /usr/src/bridge/bridge/program/nodejs clients/token_bridge/pkg/core
 COPY --from=build /usr/src/bridge/bridge/program/nodejs clients/nft_bridge/pkg/core

部分文件因文件數量過多而無法顯示