sdk/js: attempt jest tests

Change-Id: I139153994604f0048f690b011048daec4d760a55
This commit is contained in:
Evan Gray 2021-11-05 15:18:12 -04:00
parent 71e6fa7f6f
commit c824a99636
36 changed files with 9836 additions and 308 deletions

View File

@ -1,5 +1,17 @@
# Changelog # 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 ## 0.0.8
### Added ### Added

7
sdk/js/jestconfig.json Normal file
View File

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

9272
sdk/js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
import { importMigrationWasm } from "../solana/wasm";
export default async function parsePool(data: Uint8Array) { 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); return parse_pool(data);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

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

38
sdk/js/src/solana/wasm.ts Normal file
View File

@ -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();
}

View File

@ -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();
});
});

View File

@ -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
});
});

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import { Bridge__factory } from "../ethers-contracts";
import { ChainId } from "../utils"; import { ChainId } from "../utils";
import { LCDClient } from "@terra-money/terra.js"; import { LCDClient } from "@terra-money/terra.js";
import { fromUint8Array } from "js-base64"; 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 * 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, originChain: ChainId,
originAsset: Uint8Array originAsset: Uint8Array
) { ) {
const { wrapped_address } = await import("../solana/token/token_bridge"); const { wrapped_address } = await importTokenWasm();
const wrappedAddress = wrapped_address( const wrappedAddress = wrapped_address(
tokenBridgeAddress, tokenBridgeAddress,
originAsset, originAsset,

View File

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

View File

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

View File

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

View File

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

View File

@ -42,6 +42,10 @@ RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=migration/target \ --mount=type=cache,target=migration/target \
cd migration && /usr/local/cargo/bin/wasm-pack build --target bundler -d bundler -- --features wasm 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 # Compile NFT Bridge
RUN --mount=type=cache,target=/root/.cache \ RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=modules/nft_bridge/target \ --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/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/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/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/token_bridge/pkg/core
COPY --from=build /usr/src/bridge/bridge/program/nodejs clients/nft_bridge/pkg/core COPY --from=build /usr/src/bridge/bridge/program/nodejs clients/nft_bridge/pkg/core