sdk/js: terra2 support

Co-authored-by: Kevin Peters <kpeters@jumptrading.com>
This commit is contained in:
Evan Gray 2022-06-16 19:54:31 +00:00 committed by Evan Gray
parent d3a1fa99d9
commit 3dc0bac63f
12 changed files with 956 additions and 760 deletions

View File

@ -1,5 +1,16 @@
# Changelog
## 0.4.2
### Added
Neon testnet support
Terra 2 devnet support
### Changed
Updated terra.js
## 0.3.8
### Added

1277
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",
"version": "0.3.8",
"version": "0.4.2",
"description": "SDK for interacting with Wormhole",
"homepage": "https://wormholenetwork.com",
"main": "./lib/cjs/index.js",
@ -46,7 +46,7 @@
"@types/node": "^16.6.1",
"@types/react": "^17.0.19",
"copy-dir": "^1.3.0",
"ethers": "^5.4.4",
"ethers": "^5.6.8",
"jest": "^27.3.1",
"prettier": "^2.3.2",
"ts-jest": "^27.0.7",
@ -59,7 +59,7 @@
"@improbable-eng/grpc-web": "^0.14.0",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.24.0",
"@terra-money/terra.js": "^3.0.7",
"@terra-money/terra.js": "^3.1.3",
"algosdk": "^1.15.0",
"axios": "^0.24.0",
"bech32": "^2.0.0",

View File

@ -0,0 +1,9 @@
import { keccak256 } from "ethers/lib/utils";
import { isNativeDenom } from "../terra";
export function buildTokenId(address: string) {
return (
(isNativeDenom(address) ? "01" : "00") +
keccak256(Buffer.from(address, "utf-8")).substring(4)
);
}

View File

@ -1,6 +1,17 @@
import { BlockTxBroadcastResult, Coins, LCDClient, MnemonicKey, Msg, MsgExecuteContract, Fee, TxInfo, Wallet, isTxError } from "@terra-money/terra.js";
import {
Coins,
LCDClient,
MnemonicKey,
Msg,
MsgExecuteContract,
Fee,
TxInfo,
WaitTxBroadcastResult,
Wallet,
isTxError,
} from "@terra-money/terra.js";
import axios from "axios";
import Web3 from 'web3';
import Web3 from "web3";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import { describe, expect, jest, test } from "@jest/globals";
import {
@ -8,9 +19,7 @@ import {
Token,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import {
MsgInstantiateContract,
} from "@terra-money/terra.js";
import { MsgInstantiateContract } from "@terra-money/terra.js";
import { BigNumberish, ethers } from "ethers";
import {
ChainId,
@ -24,7 +33,7 @@ import {
parseSequenceFromLogSolana,
getEmitterAddressSolana,
CHAIN_ID_SOLANA,
parseNFTPayload
parseNFTPayload,
} from "../..";
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
import { importCoreWasm, setDefaultWasm } from "../../solana/wasm";
@ -51,7 +60,12 @@ import {
NFTImplementation__factory,
} from "../../ethers-contracts";
import sha3 from "js-sha3";
import { Connection, Keypair, PublicKey, TransactionResponse } from "@solana/web3.js";
import {
Connection,
Keypair,
PublicKey,
TransactionResponse,
} from "@solana/web3.js";
import { postVaaSolanaWithRetry } from "../../solana";
import { tryNativeToUint8Array } from "../../utils";
const ERC721 = require("@openzeppelin/contracts/build/contracts/ERC721PresetMinterPauserAutoId.json");
@ -66,10 +80,13 @@ type Address = string;
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const terraWallet: Wallet = lcd.wallet(new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
}));
const terraWallet: Wallet = lcd.wallet(
new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
})
);
// ethereum setup
const web3 = new Web3(ETH_NODE_URL);
@ -86,11 +103,11 @@ const payerAddress = keypair.publicKey.toString();
beforeEach(() => {
provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider); // corresponds to accounts[1]
})
});
afterEach(() => {
provider.destroy();
})
});
describe("Integration Tests", () => {
test("Send Ethereum ERC-721 to Terra and back", (done) => {
@ -102,14 +119,22 @@ describe("Integration Tests", () => {
"https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/",
11 // mint ids 0..10 (inclusive)
);
const transaction = await _transferFromEth(erc721.address, 10, terraWallet.key.accAddress, CHAIN_ID_TERRA);
const transaction = await _transferFromEth(
erc721.address,
10,
terraWallet.key.accAddress,
CHAIN_ID_TERRA
);
let signedVAA = await waitUntilEthTxObserved(transaction);
(await expectReceivedOnTerra(signedVAA)).toBe(false);
await _redeemOnTerra(signedVAA);
(await expectReceivedOnTerra(signedVAA)).toBe(true);
// Check we have the wrapped NFT contract
const terra_addr = await nft_bridge.getForeignAssetTerra(TERRA_NFT_BRIDGE_ADDRESS, lcd, CHAIN_ID_ETH,
const terra_addr = await nft_bridge.getForeignAssetTerra(
TERRA_NFT_BRIDGE_ADDRESS,
lcd,
CHAIN_ID_ETH,
tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH)
);
if (!terra_addr) {
@ -117,17 +142,28 @@ describe("Integration Tests", () => {
}
// 10 => "10"
const info: any = await lcd.wasm.contractQuery(terra_addr, { nft_info: { token_id: "10" } });
expect(info.token_uri).toBe("https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10");
const info: any = await lcd.wasm.contractQuery(terra_addr, {
nft_info: { token_id: "10" },
});
expect(info.token_uri).toBe(
"https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10"
);
const ownerInfo: any = await lcd.wasm.contractQuery(terra_addr, { owner_of: { token_id: "10" } });
const ownerInfo: any = await lcd.wasm.contractQuery(terra_addr, {
owner_of: { token_id: "10" },
});
expect(ownerInfo.owner).toBe(terraWallet.key.accAddress);
let ownerEth = await erc721.ownerOf(10);
expect(ownerEth).not.toBe(signer.address);
// Send back the NFT to ethereum
const transaction2 = await _transferFromTerra(terra_addr, "10", signer.address, CHAIN_ID_ETH);
const transaction2 = await _transferFromTerra(
terra_addr,
"10",
signer.address,
CHAIN_ID_ETH
);
signedVAA = await waitUntilTerraTxObserved(transaction2);
(await expectReceivedOnEth(signedVAA)).toBe(false);
await _redeemOnEth(signedVAA);
@ -140,7 +176,9 @@ describe("Integration Tests", () => {
// the wrapped token should no longer exist
let error;
try {
await lcd.wasm.contractQuery(terra_addr, { owner_of: { token_id: "10" } });
await lcd.wasm.contractQuery(terra_addr, {
owner_of: { token_id: "10" },
});
} catch (e) {
error = e;
}
@ -149,7 +187,9 @@ describe("Integration Tests", () => {
done();
} catch (e) {
console.error(e);
done(`An error occured while trying to transfer from Ethereum to Terra and back: ${e}`);
done(
`An error occured while trying to transfer from Ethereum to Terra and back: ${e}`
);
}
})();
});
@ -160,18 +200,26 @@ describe("Integration Tests", () => {
const cw721 = await deployNFTOnTerra(
"Integration Test NFT",
"INT",
'https://ixmfkhnh2o4keek2457f2v2iw47cugsx23eynlcfpstxihsay7nq.arweave.net/RdhVHafTuKIRWud-XVdItz4qGlfWyYasRXyndB5Ax9s/',
"https://ixmfkhnh2o4keek2457f2v2iw47cugsx23eynlcfpstxihsay7nq.arweave.net/RdhVHafTuKIRWud-XVdItz4qGlfWyYasRXyndB5Ax9s/",
token_id
);
// transfer NFT
const transaction = await _transferFromTerra(cw721, token_id, signer.address, CHAIN_ID_ETH);
const transaction = await _transferFromTerra(
cw721,
token_id,
signer.address,
CHAIN_ID_ETH
);
let signedVAA = await waitUntilTerraTxObserved(transaction);
(await expectReceivedOnEth(signedVAA)).toBe(false);
await _redeemOnEth(signedVAA);
(await expectReceivedOnEth(signedVAA)).toBe(true);
// Check we have the wrapped NFT contract
const eth_addr = await nft_bridge.getForeignAssetEth(ETH_NFT_BRIDGE_ADDRESS, provider, CHAIN_ID_TERRA,
const eth_addr = await nft_bridge.getForeignAssetEth(
ETH_NFT_BRIDGE_ADDRESS,
provider,
CHAIN_ID_TERRA,
tryNativeToUint8Array(cw721, CHAIN_ID_TERRA)
);
if (!eth_addr) {
@ -180,18 +228,25 @@ describe("Integration Tests", () => {
const token = NFTImplementation__factory.connect(eth_addr, signer);
// the id on eth will be the keccak256 hash of the terra id
const eth_id = '0x' + sha3.keccak256(token_id);
const eth_id = "0x" + sha3.keccak256(token_id);
const owner = await token.ownerOf(eth_id);
expect(owner).toBe(signer.address);
// send back the NFT to terra
const transaction2 = await _transferFromEth(eth_addr, eth_id, terraWallet.key.accAddress, CHAIN_ID_TERRA);
const transaction2 = await _transferFromEth(
eth_addr,
eth_id,
terraWallet.key.accAddress,
CHAIN_ID_TERRA
);
signedVAA = await waitUntilEthTxObserved(transaction2);
(await expectReceivedOnTerra(signedVAA)).toBe(false);
await _redeemOnTerra(signedVAA);
(await expectReceivedOnTerra(signedVAA)).toBe(true);
const ownerInfo: any = await lcd.wasm.contractQuery(cw721, { owner_of: { token_id: token_id } });
const ownerInfo: any = await lcd.wasm.contractQuery(cw721, {
owner_of: { token_id: token_id },
});
expect(ownerInfo.owner).toBe(terraWallet.key.accAddress);
// the wrapped token should no longer exist
@ -207,7 +262,9 @@ describe("Integration Tests", () => {
done();
} catch (e) {
console.error(e);
done(`An error occured while trying to transfer from Terra to Ethereum: ${e}`);
done(
`An error occured while trying to transfer from Terra to Ethereum: ${e}`
);
}
})();
});
@ -216,17 +273,19 @@ describe("Integration Tests", () => {
try {
const { parse_vaa } = await importCoreWasm();
const fromAddress = (
await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
new PublicKey(TEST_SOLANA_TOKEN),
keypair.publicKey
)
const fromAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
new PublicKey(TEST_SOLANA_TOKEN),
keypair.publicKey
);
// send to terra
const transaction1 = await _transferFromSolana(fromAddress, terraWallet.key.accAddress, CHAIN_ID_TERRA);
const transaction1 = await _transferFromSolana(
fromAddress,
terraWallet.key.accAddress,
CHAIN_ID_TERRA
);
let signedVAA = await waitUntilSolanaTxObserved(transaction1);
// we get the solana token id from the VAA
@ -235,25 +294,41 @@ describe("Integration Tests", () => {
);
await _redeemOnTerra(signedVAA);
const terra_addr = await nft_bridge.getForeignAssetTerra(TERRA_NFT_BRIDGE_ADDRESS, lcd, CHAIN_ID_SOLANA,
const terra_addr = await nft_bridge.getForeignAssetTerra(
TERRA_NFT_BRIDGE_ADDRESS,
lcd,
CHAIN_ID_SOLANA,
tryNativeToUint8Array(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA)
);
if (!terra_addr) {
throw new Error("Terra address is null");
}
// send to ethereum
const transaction2 = await _transferFromTerra(terra_addr, tokenId.toString(), signer.address, CHAIN_ID_ETH);
const transaction2 = await _transferFromTerra(
terra_addr,
tokenId.toString(),
signer.address,
CHAIN_ID_ETH
);
signedVAA = await waitUntilTerraTxObserved(transaction2);
await _redeemOnEth(signedVAA);
const eth_addr = await nft_bridge.getForeignAssetEth(ETH_NFT_BRIDGE_ADDRESS, provider, CHAIN_ID_SOLANA,
const eth_addr = await nft_bridge.getForeignAssetEth(
ETH_NFT_BRIDGE_ADDRESS,
provider,
CHAIN_ID_SOLANA,
tryNativeToUint8Array(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA)
);
if (!eth_addr) {
throw new Error("Ethereum address is null");
}
const transaction3 = await _transferFromEth(eth_addr, tokenId, fromAddress.toString(), CHAIN_ID_SOLANA);
const transaction3 = await _transferFromEth(
eth_addr,
tokenId,
fromAddress.toString(),
CHAIN_ID_SOLANA
);
signedVAA = await waitUntilEthTxObserved(transaction3);
const { name, symbol } = parseNFTPayload(
@ -261,15 +336,17 @@ describe("Integration Tests", () => {
);
// if the names match up here, it means all the spl caches work
expect(name).toBe('Not a PUNK🎸');
expect(symbol).toBe('PUNK🎸');
expect(name).toBe("Not a PUNK🎸");
expect(symbol).toBe("PUNK🎸");
await _redeemOnSolana(signedVAA);
done();
} catch (e) {
console.error(e);
done(`An error occured while trying to transfer from Solana to Ethereum: ${e}`);
done(
`An error occured while trying to transfer from Solana to Ethereum: ${e}`
);
}
})();
});
@ -282,16 +359,26 @@ describe("Integration Tests", () => {
"https://foo.com",
1
);
const transaction = await _transferFromEth(erc721.address, 0, terraWallet.key.accAddress, CHAIN_ID_TERRA);
const transaction = await _transferFromEth(
erc721.address,
0,
terraWallet.key.accAddress,
CHAIN_ID_TERRA
);
let signedVAA = await waitUntilEthTxObserved(transaction);
await _redeemOnTerra(signedVAA);
const terra_addr = await nft_bridge.getForeignAssetTerra(TERRA_NFT_BRIDGE_ADDRESS, lcd, CHAIN_ID_ETH,
const terra_addr = await nft_bridge.getForeignAssetTerra(
TERRA_NFT_BRIDGE_ADDRESS,
lcd,
CHAIN_ID_ETH,
tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH)
);
if (!terra_addr) {
throw new Error("Terra address is null");
}
const info: any = await lcd.wasm.contractQuery(terra_addr, { contract_info: {} });
const info: any = await lcd.wasm.contractQuery(terra_addr, {
contract_info: {},
});
expect(info.name).toBe("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<61>");
done();
})();
@ -301,16 +388,23 @@ describe("Integration Tests", () => {
////////////////////////////////////////////////////////////////////////////////
// Utils
async function deployNFTOnEth(name: string, symbol: string, uri: string, how_many: number): Promise<NFTImplementation> {
async function deployNFTOnEth(
name: string,
symbol: string,
uri: string,
how_many: number
): Promise<NFTImplementation> {
const accounts = await web3.eth.getAccounts();
const nftContract = new web3.eth.Contract(ERC721.abi);
let nft = await nftContract.deploy({
data: ERC721.bytecode,
arguments: [name, symbol, uri]
}).send({
from: accounts[1],
gas: 5000000,
});
let nft = await nftContract
.deploy({
data: ERC721.bytecode,
arguments: [name, symbol, uri],
})
.send({
from: accounts[1],
gas: 5000000,
});
// The eth contracts mints tokens with sequential ids, so in order to get to a
// specific id, we need to mint multiple nfts. We need this to test that
@ -326,7 +420,12 @@ async function deployNFTOnEth(name: string, symbol: string, uri: string, how_man
return NFTImplementation__factory.connect(nft.options.address, signer);
}
async function deployNFTOnTerra(name: string, symbol: string, uri: string, token_id: string): Promise<Address> {
async function deployNFTOnTerra(
name: string,
symbol: string,
uri: string,
token_id: string
): Promise<Address> {
var address: any;
await terraWallet
.createAndSignTx({
@ -356,18 +455,19 @@ async function deployNFTOnTerra(name: string, symbol: string, uri: string, token
}
async function getGasPrices() {
return axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
return axios.get(TERRA_GAS_PRICES_URL).then((result) => result.data);
}
async function estimateTerraFee(gasPrices: Coins.Input, msgs: Msg[]): Promise<Fee> {
async function estimateTerraFee(
gasPrices: Coins.Input,
msgs: Msg[]
): Promise<Fee> {
const feeEstimate = await lcd.tx.estimateFee(
[
{
sequenceNumber: await terraWallet.sequence(),
publicKey: terraWallet.key.publicKey
}
publicKey: terraWallet.key.publicKey,
},
],
{
msgs,
@ -379,7 +479,9 @@ async function estimateTerraFee(gasPrices: Coins.Input, msgs: Msg[]): Promise<Fe
return feeEstimate;
}
async function waitUntilTerraTxObserved(txresult: BlockTxBroadcastResult): Promise<Uint8Array> {
async function waitUntilTerraTxObserved(
txresult: WaitTxBroadcastResult
): Promise<Uint8Array> {
// get the sequence from the logs (needed to fetch the vaa)
const info = await waitForTerraExecution(txresult.txhash);
const sequence = parseSequenceFromLogTerra(info);
@ -397,12 +499,11 @@ async function waitUntilTerraTxObserved(txresult: BlockTxBroadcastResult): Promi
return signedVAA;
}
async function waitUntilEthTxObserved(receipt: ethers.ContractReceipt): Promise<Uint8Array> {
async function waitUntilEthTxObserved(
receipt: ethers.ContractReceipt
): Promise<Uint8Array> {
// get the sequence from the logs (needed to fetch the vaa)
let sequence = parseSequenceFromLogEth(
receipt,
ETH_CORE_BRIDGE_ADDRESS
);
let sequence = parseSequenceFromLogEth(receipt, ETH_CORE_BRIDGE_ADDRESS);
let emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS);
// poll until the guardian(s) witness and sign the vaa
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
@ -417,7 +518,9 @@ async function waitUntilEthTxObserved(receipt: ethers.ContractReceipt): Promise<
return signedVAA;
}
async function waitUntilSolanaTxObserved(response: TransactionResponse): Promise<Uint8Array> {
async function waitUntilSolanaTxObserved(
response: TransactionResponse
): Promise<Uint8Array> {
// get the sequence from the logs (needed to fetch the vaa)
const sequence = parseSequenceFromLogSolana(response);
const emitterAddress = await getEmitterAddressSolana(
@ -436,7 +539,11 @@ async function waitUntilSolanaTxObserved(response: TransactionResponse): Promise
return signedVAA;
}
async function mint_cw721(contract_address: string, token_id: string, token_uri: any): Promise<void> {
async function mint_cw721(
contract_address: string,
token_id: string,
token_uri: any
): Promise<void> {
await terraWallet
.createAndSignTx({
msgs: [
@ -473,9 +580,7 @@ async function waitForTerraExecution(txHash: string): Promise<TxInfo> {
}
if (isTxError(info)) {
// error code
throw new Error(
`Tx ${txHash}: error code ${info.code}: ${info.raw_log}`
);
throw new Error(`Tx ${txHash}: error code ${info.code}: ${info.raw_log}`);
}
return info;
}
@ -496,12 +601,17 @@ async function expectReceivedOnEth(signedVAA: Uint8Array) {
await nft_bridge.getIsTransferCompletedEth(
ETH_NFT_BRIDGE_ADDRESS,
provider,
signedVAA,
signedVAA
)
);
}
async function _transferFromEth(erc721: string, token_id: BigNumberish, address: string, chain: ChainId): Promise<ethers.ContractReceipt> {
async function _transferFromEth(
erc721: string,
token_id: BigNumberish,
address: string,
chain: ChainId
): Promise<ethers.ContractReceipt> {
return nft_bridge.transferFromEth(
ETH_NFT_BRIDGE_ADDRESS,
signer,
@ -512,7 +622,12 @@ async function _transferFromEth(erc721: string, token_id: BigNumberish, address:
);
}
async function _transferFromTerra(terra_addr: string, token_id: string, address: string, chain: ChainId): Promise<BlockTxBroadcastResult> {
async function _transferFromTerra(
terra_addr: string,
token_id: string,
address: string,
chain: ChainId
): Promise<WaitTxBroadcastResult> {
const gasPrices = await getGasPrices();
const msgs = await nft_bridge.transferFromTerra(
terraWallet.key.accAddress,
@ -520,18 +635,23 @@ async function _transferFromTerra(terra_addr: string, token_id: string, address:
terra_addr,
token_id,
chain,
tryNativeToUint8Array(address, chain))
tryNativeToUint8Array(address, chain)
);
const tx = await terraWallet.createAndSignTx({
msgs: msgs,
memo: "test",
feeDenoms: ["uluna"],
gasPrices,
fee: await estimateTerraFee(gasPrices, msgs)
fee: await estimateTerraFee(gasPrices, msgs),
});
return lcd.tx.broadcast(tx);
}
async function _transferFromSolana(fromAddress: PublicKey, targetAddress: string, chain: ChainId): Promise<TransactionResponse> {
async function _transferFromSolana(
fromAddress: PublicKey,
targetAddress: string,
chain: ChainId
): Promise<TransactionResponse> {
const transaction = await nft_bridge.transferFromSolana(
connection,
SOLANA_CORE_BRIDGE_ADDRESS,
@ -544,29 +664,24 @@ async function _transferFromSolana(fromAddress: PublicKey, targetAddress: string
);
// sign, send, and confirm transaction
transaction.partialSign(keypair);
const txid = await connection.sendRawTransaction(
transaction.serialize()
);
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"
);
throw new Error("An error occurred while fetching the transaction info");
}
return info;
}
async function _redeemOnEth(signedVAA: Uint8Array): Promise<ethers.ContractReceipt> {
return nft_bridge.redeemOnEth(
ETH_NFT_BRIDGE_ADDRESS,
signer,
signedVAA
);
async function _redeemOnEth(
signedVAA: Uint8Array
): Promise<ethers.ContractReceipt> {
return nft_bridge.redeemOnEth(ETH_NFT_BRIDGE_ADDRESS, signer, signedVAA);
}
async function _redeemOnTerra(signedVAA: Uint8Array): Promise<BlockTxBroadcastResult> {
async function _redeemOnTerra(
signedVAA: Uint8Array
): Promise<WaitTxBroadcastResult> {
const msg = await nft_bridge.redeemOnTerra(
TERRA_NFT_BRIDGE_ADDRESS,
terraWallet.key.accAddress,
@ -595,5 +710,5 @@ async function _redeemOnSolana(signedVAA: Uint8Array) {
payerAddress,
Buffer.from(signedVAA),
maxRetries
)
);
}

View File

@ -151,7 +151,8 @@ function bigToUint8Array(big: bigint) {
export async function getOriginalAssetTerra(
client: LCDClient,
wrappedAddress: string
wrappedAddress: string,
lookupChain: ChainId | ChainName
): Promise<WormholeWrappedInfo> {
try {
const result: {
@ -173,7 +174,7 @@ export async function getOriginalAssetTerra(
} catch (e) {}
return {
isWrapped: false,
chainId: CHAIN_ID_TERRA,
chainId: coalesceChainId(lookupChain),
assetAddress: zeroPad(canonicalAddress(wrappedAddress), 32),
};
}

View File

@ -52,17 +52,20 @@ export const TERRA_HOST =
URL: "https://lcd.terra.dev",
chainID: "columbus-5",
name: "mainnet",
isClassic: true,
}
: CLUSTER === "testnet"
? {
URL: "https://bombay-lcd.terra.dev",
chainID: "bombay-12",
name: "testnet",
isClassic: true,
}
: {
URL: TERRA_NODE_URL,
chainID: "columbus-5",
name: "localterra",
isClassic: true,
};
describe("consts should exist", () => {

View File

@ -7,6 +7,7 @@ import {
} from "@solana/spl-token";
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import { LCDClient, MnemonicKey, TxInfo } from "@terra-money/terra.js";
import axios from "axios";
import { ethers } from "ethers";
import {
approveEth,
@ -29,6 +30,7 @@ import {
SOLANA_PRIVATE_KEY,
SOLANA_TOKEN_BRIDGE_ADDRESS,
TERRA_CHAIN_ID,
TERRA_GAS_PRICES_URL,
TERRA_HOST,
TERRA_NODE_URL,
TERRA_PRIVATE_KEY,
@ -153,6 +155,7 @@ export async function queryBalanceOnTerra(asset: string): Promise<number> {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -186,3 +189,7 @@ export async function queryBalanceOnTerra(asset: string): Promise<number> {
return balance;
}
export async function getTerraGasPrices() {
return axios.get(TERRA_GAS_PRICES_URL).then((result) => result.data);
}

View File

@ -111,6 +111,7 @@ import {
} from "./consts";
import {
getSignedVAABySequence,
getTerraGasPrices,
queryBalanceOnTerra,
transferFromEthToSolana,
waitForTerraExecution,
@ -653,6 +654,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -663,9 +665,7 @@ describe("Integration Tests", () => {
wallet.key.accAddress,
signedVAA
);
const gasPrices = await axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
const gasPrices = await getTerraGasPrices();
const feeEstimate = await lcd.tx.estimateFee(
[
{
@ -716,6 +716,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
// Get initial wallet balances
@ -813,9 +814,7 @@ describe("Integration Tests", () => {
wallet.key.accAddress,
signedVAA
);
const gasPrices = await axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
const gasPrices = await getTerraGasPrices();
const feeEstimate = await lcd.tx.estimateFee(
[
{
@ -883,14 +882,13 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
});
const wallet = lcd.wallet(mk);
const gasPrices = await axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
const gasPrices = await getTerraGasPrices();
// deposit some tokens (separate transactions)
for (let i = 0; i < 3; i++) {
const deposit = new MsgExecuteContract(
@ -1145,6 +1143,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -1158,7 +1157,7 @@ describe("Integration Tests", () => {
TerraWalletAddress,
Asset
);
const gasPrices = await lcd.config.gasPrices;
const gasPrices = await getTerraGasPrices();
const feeEstimate = await lcd.tx.estimateFee(
[
{
@ -1231,6 +1230,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -1293,7 +1293,7 @@ describe("Integration Tests", () => {
CHAIN_ID_ETH,
hexToUint8Array(hexStr) // This needs to be ETH wallet
);
const gasPrices = await lcd.config.gasPrices;
const gasPrices = await getTerraGasPrices();
const feeEstimate = await lcd.tx.estimateFee(
[
{
@ -1376,6 +1376,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -1452,9 +1453,7 @@ describe("Integration Tests", () => {
wallet.key.accAddress,
signedVAA
);
const gasPrices = await axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
const gasPrices = await getTerraGasPrices();
const feeEstimate = await lcd.tx.estimateFee(
[
{
@ -1524,6 +1523,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -1536,7 +1536,7 @@ describe("Integration Tests", () => {
TerraWalletAddress,
CW20
);
let gasPrices = await lcd.config.gasPrices;
const gasPrices = await getTerraGasPrices();
let feeEstimate = await lcd.tx.estimateFee(
[
{
@ -1656,7 +1656,6 @@ describe("Integration Tests", () => {
CHAIN_ID_ETH,
hexToUint8Array(hexStr) // This needs to be ETH wallet
);
gasPrices = await lcd.config.gasPrices;
feeEstimate = await lcd.tx.estimateFee(
[
{
@ -1771,9 +1770,6 @@ describe("Integration Tests", () => {
wallet.key.accAddress,
signedVAA
);
gasPrices = await axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
feeEstimate = await lcd.tx.estimateFee(
[
{
@ -2354,6 +2350,7 @@ describe("Integration Tests", () => {
const lcd = new LCDClient({
URL: TERRA_NODE_URL,
chainID: TERRA_CHAIN_ID,
isClassic: true,
});
const mk = new MnemonicKey({
mnemonic: TERRA_PRIVATE_KEY,
@ -2370,7 +2367,7 @@ describe("Integration Tests", () => {
TerraWalletAddress,
Asset
);
const gasPrices = lcd.config.gasPrices;
const gasPrices = await getTerraGasPrices();
let feeEstimate = await lcd.tx.estimateFee(
[
{

View File

@ -4,6 +4,7 @@ import { Algodv2 } from "algosdk";
import { ethers } from "ethers";
import { arrayify, zeroPad } from "ethers/lib/utils";
import { decodeLocalState } from "../algorand";
import { buildTokenId } from "../cosmwasm/address";
import { TokenImplementation__factory } from "../ethers-contracts";
import { importTokenWasm } from "../solana/wasm";
import { buildNativeId, canonicalAddress, isNativeDenom } from "../terra";
@ -70,12 +71,24 @@ export async function getOriginalAssetEth(
export async function getOriginalAssetTerra(
client: LCDClient,
wrappedAddress: string
) {
return getOriginalAssetCosmWasm(client, wrappedAddress, CHAIN_ID_TERRA);
}
export async function getOriginalAssetCosmWasm(
client: LCDClient,
wrappedAddress: string,
lookupChain: ChainId | ChainName
): Promise<WormholeWrappedInfo> {
const chainId = coalesceChainId(lookupChain);
if (isNativeDenom(wrappedAddress)) {
return {
isWrapped: false,
chainId: CHAIN_ID_TERRA,
assetAddress: buildNativeId(wrappedAddress),
chainId: chainId,
assetAddress:
chainId === CHAIN_ID_TERRA
? buildNativeId(wrappedAddress)
: hexToUint8Array(buildTokenId(wrappedAddress)),
};
}
try {
@ -98,8 +111,11 @@ export async function getOriginalAssetTerra(
} catch (e) {}
return {
isWrapped: false,
chainId: CHAIN_ID_TERRA,
assetAddress: zeroPad(canonicalAddress(wrappedAddress), 32),
chainId: chainId,
assetAddress:
chainId === CHAIN_ID_TERRA
? zeroPad(canonicalAddress(wrappedAddress), 32)
: hexToUint8Array(buildTokenId(wrappedAddress)),
};
}

View File

@ -6,6 +6,7 @@ import {
nativeStringToHexAlgorand,
uint8ArrayToNativeStringAlgorand,
} from "../algorand";
import { buildTokenId } from "../cosmwasm/address";
import { canonicalAddress, humanAddress, isNativeDenom } from "../terra";
import {
ChainId,
@ -14,9 +15,11 @@ import {
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
CHAIN_ID_TERRA2,
CHAIN_ID_UNSET,
coalesceChainId,
isEVMChain,
isTerraChain,
} from "./consts";
/**
@ -64,12 +67,12 @@ export const tryUint8ArrayToNative = (
return hexZeroPad(hexValue(a), 20);
} else if (chainId === CHAIN_ID_SOLANA) {
return new PublicKey(a).toString();
} else if (chainId === CHAIN_ID_TERRA) {
} else if (isTerraChain(chainId)) {
const h = uint8ArrayToHex(a);
if (isHexNativeTerra(h)) {
return nativeTerraHexToDenom(h);
} else {
return humanAddress(a.slice(-20)); // terra expects 20 bytes, not 32
return humanAddress(chainId === CHAIN_ID_TERRA2 ? a : a.slice(-20)); // terra classic expects 20 bytes, not 32
}
} else if (chainId === CHAIN_ID_ALGORAND) {
return uint8ArrayToNativeStringAlgorand(a);
@ -179,6 +182,8 @@ export const tryNativeToHexString = (
} else {
return uint8ArrayToHex(zeroPad(canonicalAddress(address), 32));
}
} else if (chainId === CHAIN_ID_TERRA2) {
return buildTokenId(address);
} else if (chainId === CHAIN_ID_ALGORAND) {
return nativeStringToHexAlgorand(address);
} else if (chainId === CHAIN_ID_NEAR) {

View File

@ -17,6 +17,7 @@ export const CHAINS = {
near: 15,
moonbeam: 16,
neon: 17,
terra2: 18,
ropsten: 10001,
} as const;
@ -43,6 +44,8 @@ export type EVMChainName =
| "neon"
| "ropsten";
export type TerraChainName = "terra" | "terra2";
export type Contracts = {
core: string | undefined;
token_bridge: string | undefined;
@ -144,6 +147,11 @@ const MAINNET = {
token_bridge: undefined,
nft_bridge: undefined,
},
terra2: {
core: undefined,
token_bridge: undefined,
nft_bridge: undefined,
},
ropsten: {
core: undefined,
token_bridge: undefined,
@ -242,6 +250,11 @@ const TESTNET = {
token_bridge: "0xd11De1f930eA1F7Dd0290Fe3a2e35b9C91AEFb37",
nft_bridge: "0xa52Da3B1ffd258a2fFB7719a6aeE24095eEE24E2",
},
terra2: {
core: undefined,
token_bridge: undefined,
nft_bridge: undefined,
},
ropsten: {
core: "0x210c5F5e2AF958B4defFe715Dc621b7a3BA888c5",
token_bridge: "0xF174F9A837536C449321df1Ca093Bb96948D5386",
@ -340,6 +353,11 @@ const DEVNET = {
token_bridge: undefined,
nft_bridge: undefined,
},
terra2: {
core: undefined,
token_bridge: undefined,
nft_bridge: undefined,
},
ropsten: {
core: undefined,
token_bridge: undefined,
@ -403,6 +421,7 @@ export const CHAIN_ID_CELO = CHAINS["celo"];
export const CHAIN_ID_NEAR = CHAINS["near"];
export const CHAIN_ID_MOONBEAM = CHAINS["moonbeam"];
export const CHAIN_ID_NEON = CHAINS["neon"];
export const CHAIN_ID_TERRA2 = CHAINS["terra2"];
export const CHAIN_ID_ETHEREUM_ROPSTEN = CHAINS["ropsten"];
// This inverts the [[CHAINS]] object so that we can look up a chain by id
@ -423,6 +442,8 @@ export const CHAIN_ID_TO_NAME: ChainIdToName = Object.entries(CHAINS).reduce(
*/
export type EVMChainId = typeof CHAINS[EVMChainName];
export type TerraChainId = typeof CHAINS[TerraChainName];
/**
*
* Returns true when called with a valid chain, and narrows the type in the
@ -520,6 +541,13 @@ export function isEVMChain(
}
}
export function isTerraChain(
chain: ChainId | ChainName
): chain is TerraChainId | TerraChainName {
const chainId = coalesceChainId(chain);
return chainId === CHAIN_ID_TERRA || chainId === CHAIN_ID_TERRA2;
}
/**
*
* Asserts that the given chain id or chain name is an EVM chain, and throws otherwise.
@ -539,6 +567,7 @@ export const WSOL_ADDRESS = "So11111111111111111111111111111111111111112";
export const WSOL_DECIMALS = 9;
export const MAX_VAA_DECIMALS = 8;
// TODO: will this work for terra2?
export const TERRA_REDEEMED_CHECK_WALLET_ADDRESS =
"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v";