sdk: improve type safety + add documentation (#1102)
* sdk/js: improve type safety + add documentation * sdk/js: add support for chain names * sdk/js: fix boolean conditional bug in transferFromSolana * sdk/js: remove unsafe ChainId coercions in sdk * sdk/js: Chain id 0 This is a legit chain id that some governance VAAs target * sdk/js: Add mainnet and testnet contract addresses * sdk/js: bump version to 0.3.0 * sdk/js: limit minor version changes * sdk/js: update contracts and add devnet Co-authored-by: Csongor Kiss <ckiss@jumptrading.com> Co-authored-by: Evan Gray <battledingo@gmail.com>
This commit is contained in:
parent
c13bdff820
commit
7f427133cc
|
@ -1,5 +1,37 @@
|
|||
# Changelog
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Added
|
||||
|
||||
Added `tryNativeToHexString`
|
||||
|
||||
Added `tryNativeToUint8Array`
|
||||
|
||||
Added `tryHexToNativeString`
|
||||
|
||||
Added `tryUint8ArrayToNative`
|
||||
|
||||
Added support for passing in chain names wherever a chain is expected
|
||||
|
||||
Added chain id 0 (unset)
|
||||
|
||||
Added contract addresses to the `consts` module
|
||||
|
||||
### Changed
|
||||
|
||||
Deprecated `nativeToHexString`
|
||||
|
||||
Deprecated `hexToNativeString`
|
||||
|
||||
Deprecated `hexToNativeAssetString`
|
||||
|
||||
Deprecated `uint8ArrayToNative`
|
||||
|
||||
`isEVMChain` now performs type narrowing
|
||||
|
||||
`CHAIN_ID_*` constants now have literal types
|
||||
|
||||
## 0.2.7
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@certusone/wormhole-sdk",
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.0",
|
||||
"description": "SDK for interacting with Wormhole",
|
||||
"homepage": "https://wormholenetwork.com",
|
||||
"main": "./lib/cjs/index.js",
|
||||
|
|
|
@ -882,8 +882,12 @@ export async function _submitVAAAlgorand(
|
|||
return txs;
|
||||
}
|
||||
|
||||
export function uint8ArrayToNativeStringAlgorand(a: Uint8Array): string {
|
||||
return encodeAddress(a);
|
||||
}
|
||||
|
||||
export function hexToNativeStringAlgorand(s: string): string {
|
||||
return encodeAddress(hexToUint8Array(s));
|
||||
return uint8ArrayToNativeStringAlgorand(hexToUint8Array(s));
|
||||
}
|
||||
|
||||
export function nativeStringToHexAlgorand(s: string): string {
|
||||
|
|
|
@ -18,8 +18,6 @@ import {
|
|||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressTerra,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogTerra,
|
||||
nft_bridge,
|
||||
|
@ -29,7 +27,7 @@ import {
|
|||
parseNFTPayload
|
||||
} from "../..";
|
||||
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
||||
import { importCoreWasm, importNftWasm, setDefaultWasm } from "../../solana/wasm";
|
||||
import { importCoreWasm, setDefaultWasm } from "../../solana/wasm";
|
||||
import {
|
||||
ETH_CORE_BRIDGE_ADDRESS,
|
||||
ETH_NODE_URL,
|
||||
|
@ -55,6 +53,7 @@ import {
|
|||
import sha3 from "js-sha3";
|
||||
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");
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
@ -111,9 +110,8 @@ describe("Integration Tests", () => {
|
|||
|
||||
// Check we have the wrapped NFT contract
|
||||
const terra_addr = await nft_bridge.getForeignAssetTerra(TERRA_NFT_BRIDGE_ADDRESS, lcd, CHAIN_ID_ETH,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(erc721.address, CHAIN_ID_ETH) || ""
|
||||
));
|
||||
tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH)
|
||||
);
|
||||
if (!terra_addr) {
|
||||
throw new Error("Terra address is null");
|
||||
}
|
||||
|
@ -174,9 +172,8 @@ describe("Integration Tests", () => {
|
|||
|
||||
// Check we have the wrapped NFT contract
|
||||
const eth_addr = await nft_bridge.getForeignAssetEth(ETH_NFT_BRIDGE_ADDRESS, provider, CHAIN_ID_TERRA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(cw721, CHAIN_ID_TERRA) || ""
|
||||
));
|
||||
tryNativeToUint8Array(cw721, CHAIN_ID_TERRA)
|
||||
);
|
||||
if (!eth_addr) {
|
||||
throw new Error("Ethereum address is null");
|
||||
}
|
||||
|
@ -239,9 +236,8 @@ describe("Integration Tests", () => {
|
|||
|
||||
await _redeemOnTerra(signedVAA);
|
||||
const terra_addr = await nft_bridge.getForeignAssetTerra(TERRA_NFT_BRIDGE_ADDRESS, lcd, CHAIN_ID_SOLANA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA) || ""
|
||||
));
|
||||
tryNativeToUint8Array(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA)
|
||||
);
|
||||
if (!terra_addr) {
|
||||
throw new Error("Terra address is null");
|
||||
}
|
||||
|
@ -251,9 +247,8 @@ describe("Integration Tests", () => {
|
|||
|
||||
await _redeemOnEth(signedVAA);
|
||||
const eth_addr = await nft_bridge.getForeignAssetEth(ETH_NFT_BRIDGE_ADDRESS, provider, CHAIN_ID_SOLANA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA) || ""
|
||||
));
|
||||
tryNativeToUint8Array(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA)
|
||||
);
|
||||
if (!eth_addr) {
|
||||
throw new Error("Ethereum address is null");
|
||||
}
|
||||
|
@ -291,9 +286,8 @@ describe("Integration Tests", () => {
|
|||
let signedVAA = await waitUntilEthTxObserved(transaction);
|
||||
await _redeemOnTerra(signedVAA);
|
||||
const terra_addr = await nft_bridge.getForeignAssetTerra(TERRA_NFT_BRIDGE_ADDRESS, lcd, CHAIN_ID_ETH,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(erc721.address, CHAIN_ID_ETH) || ""
|
||||
));
|
||||
tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH)
|
||||
);
|
||||
if (!terra_addr) {
|
||||
throw new Error("Terra address is null");
|
||||
}
|
||||
|
@ -514,9 +508,8 @@ async function _transferFromEth(erc721: string, token_id: BigNumberish, address:
|
|||
erc721,
|
||||
token_id,
|
||||
chain,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(address, chain) || ""
|
||||
));
|
||||
tryNativeToUint8Array(address, chain)
|
||||
);
|
||||
}
|
||||
|
||||
async function _transferFromTerra(terra_addr: string, token_id: string, address: string, chain: ChainId): Promise<BlockTxBroadcastResult> {
|
||||
|
@ -527,7 +520,7 @@ async function _transferFromTerra(terra_addr: string, token_id: string, address:
|
|||
terra_addr,
|
||||
token_id,
|
||||
chain,
|
||||
hexToUint8Array(nativeToHexString(address, chain) || ""));
|
||||
tryNativeToUint8Array(address, chain))
|
||||
const tx = await terraWallet.createAndSignTx({
|
||||
msgs: msgs,
|
||||
memo: "test",
|
||||
|
@ -546,9 +539,7 @@ async function _transferFromSolana(fromAddress: PublicKey, targetAddress: string
|
|||
payerAddress,
|
||||
fromAddress.toString(),
|
||||
TEST_SOLANA_TOKEN,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(targetAddress, chain) || ""
|
||||
),
|
||||
tryNativeToUint8Array(targetAddress, chain),
|
||||
chain
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
|
|
|
@ -5,7 +5,7 @@ import { fromUint8Array } from "js-base64";
|
|||
import { CHAIN_ID_SOLANA } from "..";
|
||||
import { NFTBridge__factory } from "../ethers-contracts";
|
||||
import { importNftWasm } from "../solana/wasm";
|
||||
import { ChainId } from "../utils";
|
||||
import { ChainId, ChainName, coalesceChainId } from "../utils";
|
||||
|
||||
/**
|
||||
* Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
|
||||
|
@ -18,12 +18,13 @@ import { ChainId } from "../utils";
|
|||
export async function getForeignAssetEth(
|
||||
tokenBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
originChain: ChainId,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array
|
||||
): Promise<string | null> {
|
||||
const originChainId = coalesceChainId(originChain);
|
||||
const tokenBridge = NFTBridge__factory.connect(tokenBridgeAddress, provider);
|
||||
try {
|
||||
if (originChain === CHAIN_ID_SOLANA) {
|
||||
if (originChainId === CHAIN_ID_SOLANA) {
|
||||
// All NFTs from Solana are minted to the same address, the originAsset is encoded as the tokenId as
|
||||
// BigNumber.from(new PublicKey(originAsset).toBytes()).toString()
|
||||
const addr = await tokenBridge.wrappedAsset(
|
||||
|
@ -32,7 +33,7 @@ export async function getForeignAssetEth(
|
|||
);
|
||||
return addr;
|
||||
}
|
||||
return await tokenBridge.wrappedAsset(originChain, originAsset);
|
||||
return await tokenBridge.wrappedAsset(originChainId, originAsset);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -52,6 +53,7 @@ export async function getForeignAssetTerra(
|
|||
originChain: ChainId,
|
||||
originAsset: Uint8Array
|
||||
): Promise<string | null> {
|
||||
const originChainId = coalesceChainId(originChain);
|
||||
try {
|
||||
const address =
|
||||
originChain == CHAIN_ID_SOLANA
|
||||
|
@ -61,7 +63,7 @@ export async function getForeignAssetTerra(
|
|||
tokenBridgeAddress,
|
||||
{
|
||||
wrapped_registry: {
|
||||
chain: originChain,
|
||||
chain: originChainId,
|
||||
address,
|
||||
},
|
||||
}
|
||||
|
@ -81,15 +83,16 @@ export async function getForeignAssetTerra(
|
|||
*/
|
||||
export async function getForeignAssetSol(
|
||||
tokenBridgeAddress: string,
|
||||
originChain: ChainId,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array,
|
||||
tokenId: Uint8Array
|
||||
): Promise<string> {
|
||||
const originChainId = coalesceChainId(originChain);
|
||||
const { wrapped_address } = await importNftWasm();
|
||||
const wrappedAddress = wrapped_address(
|
||||
tokenBridgeAddress,
|
||||
originAsset,
|
||||
originChain,
|
||||
originChainId,
|
||||
tokenId
|
||||
);
|
||||
const wrappedAddressPK = new PublicKey(wrappedAddress);
|
||||
|
|
|
@ -5,9 +5,16 @@ import { arrayify, zeroPad } from "ethers/lib/utils";
|
|||
import { canonicalAddress, WormholeWrappedInfo } from "..";
|
||||
import { TokenImplementation__factory } from "../ethers-contracts";
|
||||
import { importNftWasm } from "../solana/wasm";
|
||||
import { ChainId, CHAIN_ID_SOLANA, CHAIN_ID_TERRA } from "../utils";
|
||||
import {
|
||||
ChainId,
|
||||
ChainName,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
coalesceChainId,
|
||||
} from "../utils";
|
||||
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
|
||||
|
||||
// TODO: remove `as ChainId` and return number in next minor version as we can't ensure it will match our type definition
|
||||
export interface WormholeWrappedNFTInfo {
|
||||
isWrapped: boolean;
|
||||
chainId: ChainId;
|
||||
|
@ -27,7 +34,7 @@ export async function getOriginalAssetEth(
|
|||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
wrappedAddress: string,
|
||||
tokenId: string,
|
||||
lookupChainId: ChainId
|
||||
lookupChain: ChainId | ChainName
|
||||
): Promise<WormholeWrappedNFTInfo> {
|
||||
const isWrapped = await getIsWrappedAssetEth(
|
||||
tokenBridgeAddress,
|
||||
|
@ -53,7 +60,7 @@ export async function getOriginalAssetEth(
|
|||
}
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: lookupChainId,
|
||||
chainId: coalesceChainId(lookupChain),
|
||||
assetAddress: zeroPad(arrayify(wrappedAddress), 32),
|
||||
tokenId,
|
||||
};
|
||||
|
|
|
@ -8,17 +8,18 @@ import {
|
|||
} from "../ethers-contracts";
|
||||
import { getBridgeFeeIx, ixFromRust } from "../solana";
|
||||
import { importNftWasm } from "../solana/wasm";
|
||||
import { ChainId, CHAIN_ID_SOLANA, createNonce } from "../utils";
|
||||
import { ChainId, ChainName, CHAIN_ID_SOLANA, coalesceChainId, createNonce } from "../utils";
|
||||
|
||||
export async function transferFromEth(
|
||||
tokenBridgeAddress: string,
|
||||
signer: ethers.Signer,
|
||||
tokenAddress: string,
|
||||
tokenID: ethers.BigNumberish,
|
||||
recipientChain: ChainId,
|
||||
recipientChain: ChainId | ChainName,
|
||||
recipientAddress: Uint8Array,
|
||||
overrides: Overrides & { from?: string | Promise<string> } = {}
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const recipientChainId = coalesceChainId(recipientChain)
|
||||
//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, overrides)).wait();
|
||||
|
@ -26,7 +27,7 @@ export async function transferFromEth(
|
|||
const v = await bridge.transferNFT(
|
||||
tokenAddress,
|
||||
tokenID,
|
||||
recipientChain,
|
||||
recipientChainId,
|
||||
recipientAddress,
|
||||
createNonce(),
|
||||
overrides
|
||||
|
@ -43,11 +44,12 @@ export async function transferFromSolana(
|
|||
fromAddress: string,
|
||||
mintAddress: string,
|
||||
targetAddress: Uint8Array,
|
||||
targetChain: ChainId,
|
||||
targetChain: ChainId | ChainName,
|
||||
originAddress?: Uint8Array,
|
||||
originChain?: ChainId,
|
||||
originChain?: ChainId | ChainName,
|
||||
originTokenId?: Uint8Array
|
||||
): Promise<Transaction> {
|
||||
const originChainId: ChainId | undefined = originChain ? coalesceChainId(originChain) : undefined
|
||||
const nonce = createNonce().readUInt32LE(0);
|
||||
const transferIx = await getBridgeFeeIx(
|
||||
connection,
|
||||
|
@ -70,7 +72,7 @@ export async function transferFromSolana(
|
|||
let messageKey = Keypair.generate();
|
||||
const isSolanaNative =
|
||||
originChain === undefined || originChain === CHAIN_ID_SOLANA;
|
||||
if (!isSolanaNative && !originAddress && !originTokenId) {
|
||||
if (!isSolanaNative && (!originAddress || !originTokenId)) {
|
||||
throw new Error(
|
||||
"originAddress and originTokenId are required when specifying originChain"
|
||||
);
|
||||
|
@ -86,7 +88,7 @@ export async function transferFromSolana(
|
|||
mintAddress,
|
||||
nonce,
|
||||
targetAddress,
|
||||
targetChain
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
: transfer_wrapped_ix(
|
||||
tokenBridgeAddress,
|
||||
|
@ -95,12 +97,12 @@ export async function transferFromSolana(
|
|||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
payerAddress,
|
||||
originChain as number, // checked by isSolanaNative
|
||||
originChainId as number, // checked by isSolanaNative
|
||||
originAddress as Uint8Array, // checked by throw
|
||||
originTokenId as Uint8Array, // checked by throw
|
||||
nonce,
|
||||
targetAddress,
|
||||
targetChain
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, approvalIx, ix);
|
||||
|
@ -116,9 +118,10 @@ export async function transferFromTerra(
|
|||
tokenBridgeAddress: string,
|
||||
tokenAddress: string,
|
||||
tokenID: string,
|
||||
recipientChain: ChainId,
|
||||
recipientChain: ChainId | ChainName,
|
||||
recipientAddress: Uint8Array
|
||||
): Promise<MsgExecuteContract[]> {
|
||||
const recipientChainId = coalesceChainId(recipientChain)
|
||||
const nonce = Math.round(Math.random() * 100000);
|
||||
return [
|
||||
new MsgExecuteContract(
|
||||
|
@ -139,7 +142,7 @@ export async function transferFromTerra(
|
|||
initiate_transfer: {
|
||||
contract_addr: tokenAddress,
|
||||
token_id: tokenID,
|
||||
recipient_chain: recipientChain,
|
||||
recipient_chain: recipientChainId,
|
||||
recipient: Buffer.from(recipientAddress).toString("base64"),
|
||||
nonce: nonce,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChainId } from "../utils/consts";
|
||||
import { ChainId, ChainName, coalesceChainId } from "../utils/consts";
|
||||
import {
|
||||
GrpcWebImpl,
|
||||
PublicRPCServiceClientImpl,
|
||||
|
@ -6,7 +6,7 @@ import {
|
|||
|
||||
export async function getSignedVAA(
|
||||
host: string,
|
||||
emitterChain: ChainId,
|
||||
emitterChain: ChainId | ChainName,
|
||||
emitterAddress: string,
|
||||
sequence: string,
|
||||
extraGrpcOpts = {}
|
||||
|
@ -15,7 +15,7 @@ export async function getSignedVAA(
|
|||
const api = new PublicRPCServiceClientImpl(rpc);
|
||||
return await api.GetSignedVAA({
|
||||
messageId: {
|
||||
emitterChain,
|
||||
emitterChain: coalesceChainId(emitterChain),
|
||||
emitterAddress,
|
||||
sequence,
|
||||
},
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { ChainId, getSignedVAA } from "..";
|
||||
import { ChainId, ChainName, getSignedVAA } from "..";
|
||||
import { coalesceChainId } from "../utils";
|
||||
|
||||
export async function getSignedVAAWithRetry(
|
||||
hosts: string[],
|
||||
emitterChain: ChainId,
|
||||
emitterChain: ChainId | ChainName,
|
||||
emitterAddress: string,
|
||||
sequence: string,
|
||||
extraGrpcOpts = {},
|
||||
|
@ -19,7 +20,7 @@ export async function getSignedVAAWithRetry(
|
|||
try {
|
||||
result = await getSignedVAA(
|
||||
hosts[getNextRpcHost()],
|
||||
emitterChain,
|
||||
coalesceChainId(emitterChain),
|
||||
emitterAddress,
|
||||
sequence,
|
||||
extraGrpcOpts
|
||||
|
|
|
@ -71,6 +71,8 @@ import {
|
|||
transferFromEth,
|
||||
transferFromSolana,
|
||||
transferFromTerra,
|
||||
tryNativeToHexString,
|
||||
tryNativeToUint8Array,
|
||||
uint8ArrayToHex,
|
||||
updateWrappedOnEth,
|
||||
WormholeWrappedInfo,
|
||||
|
@ -211,7 +213,7 @@ describe("Integration Tests", () => {
|
|||
connection,
|
||||
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||
CHAIN_ID_ETH,
|
||||
hexToUint8Array(nativeToHexString(TEST_ERC20, CHAIN_ID_ETH) || "")
|
||||
tryNativeToUint8Array(TEST_ERC20, CHAIN_ID_ETH)
|
||||
);
|
||||
const solanaMintKey = new PublicKey(SolanaForeignAsset || "");
|
||||
const recipient = await Token.getAssociatedTokenAddress(
|
||||
|
@ -294,9 +296,7 @@ describe("Integration Tests", () => {
|
|||
TEST_ERC20,
|
||||
amount,
|
||||
CHAIN_ID_SOLANA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(recipient.toString(), CHAIN_ID_SOLANA) || ""
|
||||
)
|
||||
tryNativeToUint8Array(recipient.toString(), CHAIN_ID_SOLANA)
|
||||
);
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
|
@ -503,7 +503,7 @@ describe("Integration Tests", () => {
|
|||
// Get the initial wallet balance on Eth
|
||||
const ETH_TEST_WALLET_PUBLIC_KEY =
|
||||
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
||||
const originAssetHex = nativeToHexString(
|
||||
const originAssetHex = tryNativeToHexString(
|
||||
TEST_SOLANA_TOKEN,
|
||||
CHAIN_ID_SOLANA
|
||||
);
|
||||
|
@ -538,9 +538,7 @@ describe("Integration Tests", () => {
|
|||
fromAddress,
|
||||
TEST_SOLANA_TOKEN,
|
||||
amount,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(targetAddress, CHAIN_ID_ETH) || ""
|
||||
),
|
||||
tryNativeToUint8Array(targetAddress, CHAIN_ID_ETH),
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
|
@ -737,7 +735,7 @@ describe("Integration Tests", () => {
|
|||
);
|
||||
|
||||
// Get initial balance of ERC20 on Terra
|
||||
const originAssetHex = nativeToHexString(ERC20, CHAIN_ID_ETH);
|
||||
const originAssetHex = tryNativeToHexString(ERC20, CHAIN_ID_ETH);
|
||||
if (!originAssetHex) {
|
||||
throw new Error("originAssetHex is null");
|
||||
}
|
||||
|
@ -794,9 +792,7 @@ describe("Integration Tests", () => {
|
|||
TEST_ERC20,
|
||||
amount,
|
||||
CHAIN_ID_TERRA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(wallet.key.accAddress, CHAIN_ID_TERRA) || ""
|
||||
)
|
||||
tryNativeToUint8Array(wallet.key.accAddress, CHAIN_ID_TERRA)
|
||||
);
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
|
@ -1279,7 +1275,7 @@ describe("Integration Tests", () => {
|
|||
ETH_NODE_URL
|
||||
) as any;
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
const originAssetHex = nativeToHexString(Asset, CHAIN_ID_TERRA);
|
||||
const originAssetHex = tryNativeToHexString(Asset, CHAIN_ID_TERRA);
|
||||
if (!originAssetHex) {
|
||||
throw new Error("originAssetHex is null");
|
||||
}
|
||||
|
@ -1308,7 +1304,7 @@ describe("Integration Tests", () => {
|
|||
);
|
||||
|
||||
// Start transfer from Terra to Ethereum
|
||||
const hexStr = nativeToHexString(
|
||||
const hexStr = tryNativeToHexString(
|
||||
ETH_TEST_WALLET_PUBLIC_KEY,
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
|
@ -1427,7 +1423,7 @@ describe("Integration Tests", () => {
|
|||
ETH_NODE_URL
|
||||
) as any;
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
const originAssetHex = nativeToHexString(Asset, CHAIN_ID_TERRA);
|
||||
const originAssetHex = tryNativeToHexString(Asset, CHAIN_ID_TERRA);
|
||||
if (!originAssetHex) {
|
||||
throw new Error("originAssetHex is null");
|
||||
}
|
||||
|
@ -1470,9 +1466,7 @@ describe("Integration Tests", () => {
|
|||
foreignAsset,
|
||||
Amount,
|
||||
CHAIN_ID_TERRA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(wallet.key.accAddress, CHAIN_ID_TERRA) || ""
|
||||
)
|
||||
tryNativeToUint8Array(wallet.key.accAddress, CHAIN_ID_TERRA)
|
||||
);
|
||||
console.log("Transfer gas used: ", receipt.gasUsed);
|
||||
|
||||
|
@ -1659,7 +1653,7 @@ describe("Integration Tests", () => {
|
|||
console.log("Initial Terra balance of", FeeAsset, initialFeeBalance);
|
||||
|
||||
// Get wallet on eth
|
||||
const originAssetHex = nativeToHexString(CW20, CHAIN_ID_TERRA);
|
||||
const originAssetHex = tryNativeToHexString(CW20, CHAIN_ID_TERRA);
|
||||
console.log("CW20 originAssetHex: ", originAssetHex);
|
||||
if (!originAssetHex) {
|
||||
throw new Error("originAssetHex is null");
|
||||
|
@ -1706,7 +1700,7 @@ describe("Integration Tests", () => {
|
|||
"CW20 balance on Terra before transfer = ",
|
||||
initialCW20BalOnTerra
|
||||
);
|
||||
const hexStr = nativeToHexString(
|
||||
const hexStr = tryNativeToHexString(
|
||||
ETH_TEST_WALLET_PUBLIC_KEY,
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
|
@ -1831,9 +1825,7 @@ describe("Integration Tests", () => {
|
|||
foreignAsset,
|
||||
Amount,
|
||||
CHAIN_ID_TERRA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(wallet.key.accAddress, CHAIN_ID_TERRA) || ""
|
||||
)
|
||||
tryNativeToUint8Array(wallet.key.accAddress, CHAIN_ID_TERRA)
|
||||
);
|
||||
console.log("Transfer gas used: ", receipt.gasUsed);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function attestFromEth(
|
|||
signer: ethers.Signer,
|
||||
tokenAddress: string,
|
||||
overrides: PayableOverrides & { from?: string | Promise<string> } = {}
|
||||
) {
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
||||
const v = await bridge.attestToken(tokenAddress, createNonce(), overrides);
|
||||
const receipt = await v.wait();
|
||||
|
@ -36,7 +36,7 @@ export async function attestFromTerra(
|
|||
tokenBridgeAddress: string,
|
||||
walletAddress: string,
|
||||
asset: string
|
||||
) {
|
||||
): Promise<MsgExecuteContract> {
|
||||
const nonce = Math.round(Math.random() * 100000);
|
||||
const isNativeAsset = isNativeDenom(asset);
|
||||
return new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
||||
|
@ -61,7 +61,7 @@ export async function attestFromSolana(
|
|||
tokenBridgeAddress: string,
|
||||
payerAddress: string,
|
||||
mintAddress: string
|
||||
) {
|
||||
): Promise<Transaction> {
|
||||
const nonce = createNonce().readUInt32LE(0);
|
||||
const transferIx = await getBridgeFeeIx(
|
||||
connection,
|
||||
|
|
|
@ -13,7 +13,7 @@ export async function createWrappedOnEth(
|
|||
signer: ethers.Signer,
|
||||
signedVAA: Uint8Array,
|
||||
overrides: Overrides & { from?: string | Promise<string> } = {}
|
||||
) {
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
||||
const v = await bridge.createWrapped(signedVAA, overrides);
|
||||
const receipt = await v.wait();
|
||||
|
@ -24,7 +24,7 @@ export async function createWrappedOnTerra(
|
|||
tokenBridgeAddress: string,
|
||||
walletAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
): Promise<MsgExecuteContract> {
|
||||
return new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
|
@ -38,7 +38,7 @@ export async function createWrappedOnSolana(
|
|||
tokenBridgeAddress: string,
|
||||
payerAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
): Promise<Transaction> {
|
||||
const { create_wrapped_ix } = await importTokenWasm();
|
||||
const ix = ixFromRust(
|
||||
create_wrapped_ix(
|
||||
|
|
|
@ -10,7 +10,12 @@ import {
|
|||
} from "../algorand";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { importTokenWasm } from "../solana/wasm";
|
||||
import { ChainId, CHAIN_ID_ALGORAND } from "../utils";
|
||||
import {
|
||||
ChainId,
|
||||
ChainName,
|
||||
CHAIN_ID_ALGORAND,
|
||||
coalesceChainId,
|
||||
} from "../utils";
|
||||
|
||||
/**
|
||||
* Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
|
||||
|
@ -23,12 +28,15 @@ import { ChainId, CHAIN_ID_ALGORAND } from "../utils";
|
|||
export async function getForeignAssetEth(
|
||||
tokenBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
originChain: ChainId,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array
|
||||
) {
|
||||
): Promise<string | null> {
|
||||
const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
|
||||
try {
|
||||
return await tokenBridge.wrappedAsset(originChain, originAsset);
|
||||
return await tokenBridge.wrappedAsset(
|
||||
coalesceChainId(originChain),
|
||||
originAsset
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -37,15 +45,15 @@ export async function getForeignAssetEth(
|
|||
export async function getForeignAssetTerra(
|
||||
tokenBridgeAddress: string,
|
||||
client: LCDClient,
|
||||
originChain: ChainId,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array
|
||||
) {
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const result: { address: string } = await client.wasm.contractQuery(
|
||||
tokenBridgeAddress,
|
||||
{
|
||||
wrapped_registry: {
|
||||
chain: originChain,
|
||||
chain: coalesceChainId(originChain),
|
||||
address: fromUint8Array(originAsset),
|
||||
},
|
||||
}
|
||||
|
@ -67,14 +75,14 @@ export async function getForeignAssetTerra(
|
|||
export async function getForeignAssetSolana(
|
||||
connection: Connection,
|
||||
tokenBridgeAddress: string,
|
||||
originChain: ChainId,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array
|
||||
) {
|
||||
): Promise<string | null> {
|
||||
const { wrapped_address } = await importTokenWasm();
|
||||
const wrappedAddress = wrapped_address(
|
||||
tokenBridgeAddress,
|
||||
originAsset,
|
||||
originChain
|
||||
coalesceChainId(originChain)
|
||||
);
|
||||
const wrappedAddressPK = new PublicKey(wrappedAddress);
|
||||
const wrappedAssetAccountInfo = await connection.getAccountInfo(
|
||||
|
@ -86,16 +94,17 @@ export async function getForeignAssetSolana(
|
|||
export async function getForeignAssetAlgorand(
|
||||
client: Algodv2,
|
||||
tokenBridgeId: bigint,
|
||||
chain: ChainId,
|
||||
chain: ChainId | ChainName,
|
||||
contract: string
|
||||
): Promise<bigint | null> {
|
||||
if (chain === CHAIN_ID_ALGORAND) {
|
||||
const chainId = coalesceChainId(chain);
|
||||
if (chainId === CHAIN_ID_ALGORAND) {
|
||||
return hexToNativeAssetBigIntAlgorand(contract);
|
||||
} else {
|
||||
let { lsa, doesExist } = await calcLogicSigAccount(
|
||||
client,
|
||||
tokenBridgeId,
|
||||
BigInt(chain),
|
||||
BigInt(chainId),
|
||||
contract
|
||||
);
|
||||
if (!doesExist) {
|
||||
|
|
|
@ -20,7 +20,7 @@ export async function getIsTransferCompletedEth(
|
|||
tokenBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
|
||||
const signedVAAHash = await getSignedVAAHash(signedVAA);
|
||||
return await tokenBridge.isTransferCompleted(signedVAAHash);
|
||||
|
@ -31,7 +31,7 @@ export async function getIsTransferCompletedTerra(
|
|||
signedVAA: Uint8Array,
|
||||
client: LCDClient,
|
||||
gasPriceUrl: string
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
const msg = await redeemOnTerra(
|
||||
tokenBridgeAddress,
|
||||
TERRA_REDEEMED_CHECK_WALLET_ADDRESS,
|
||||
|
@ -68,7 +68,7 @@ export async function getIsTransferCompletedSolana(
|
|||
tokenBridgeAddress: string,
|
||||
signedVAA: Uint8Array,
|
||||
connection: Connection
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
const { claim_address } = await importCoreWasm();
|
||||
const claimAddress = await claim_address(tokenBridgeAddress, signedVAA);
|
||||
const claimInfo = await connection.getAccountInfo(
|
||||
|
|
|
@ -17,17 +17,18 @@ export async function getIsWrappedAssetEth(
|
|||
tokenBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
assetAddress: string
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
if (!assetAddress) return false;
|
||||
const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
|
||||
return await tokenBridge.isWrappedAsset(assetAddress);
|
||||
}
|
||||
|
||||
// TODO: this doesn't seem right
|
||||
export async function getIsWrappedAssetTerra(
|
||||
tokenBridgeAddress: string,
|
||||
client: LCDClient,
|
||||
assetAddress: string
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -42,7 +43,7 @@ export async function getIsWrappedAssetSol(
|
|||
connection: Connection,
|
||||
tokenBridgeAddress: string,
|
||||
mintAddress: string
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
if (!mintAddress) return false;
|
||||
const { wrapped_meta_address } = await importTokenWasm();
|
||||
const wrappedMetaAddress = wrapped_meta_address(
|
||||
|
|
|
@ -9,9 +9,11 @@ import { importTokenWasm } from "../solana/wasm";
|
|||
import { buildNativeId, canonicalAddress, isNativeDenom } from "../terra";
|
||||
import {
|
||||
ChainId,
|
||||
ChainName,
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
coalesceChainId,
|
||||
hexToUint8Array,
|
||||
} from "../utils";
|
||||
import { safeBigIntToNumber } from "../utils/bigint";
|
||||
|
@ -20,6 +22,7 @@ import {
|
|||
getIsWrappedAssetEth,
|
||||
} from "./getIsWrappedAsset";
|
||||
|
||||
// TODO: remove `as ChainId` and return number in next minor version as we can't ensure it will match our type definition
|
||||
export interface WormholeWrappedInfo {
|
||||
isWrapped: boolean;
|
||||
chainId: ChainId;
|
||||
|
@ -37,7 +40,7 @@ export async function getOriginalAssetEth(
|
|||
tokenBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
wrappedAddress: string,
|
||||
lookupChainId: ChainId
|
||||
lookupChain: ChainId | ChainName
|
||||
): Promise<WormholeWrappedInfo> {
|
||||
const isWrapped = await getIsWrappedAssetEth(
|
||||
tokenBridgeAddress,
|
||||
|
@ -59,7 +62,7 @@ export async function getOriginalAssetEth(
|
|||
}
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: lookupChainId,
|
||||
chainId: coalesceChainId(lookupChain),
|
||||
assetAddress: zeroPad(arrayify(wrappedAddress), 32),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ import { getBridgeFeeIx, ixFromRust } from "../solana";
|
|||
import { importTokenWasm } from "../solana/wasm";
|
||||
import {
|
||||
ChainId,
|
||||
ChainName,
|
||||
CHAIN_ID_SOLANA,
|
||||
coalesceChainId,
|
||||
createNonce,
|
||||
hexToUint8Array,
|
||||
textToUint8Array,
|
||||
|
@ -73,16 +75,17 @@ export async function transferFromEth(
|
|||
signer: ethers.Signer,
|
||||
tokenAddress: string,
|
||||
amount: ethers.BigNumberish,
|
||||
recipientChain: ChainId,
|
||||
recipientChain: ChainId | ChainName,
|
||||
recipientAddress: Uint8Array,
|
||||
relayerFee: ethers.BigNumberish = 0,
|
||||
overrides: PayableOverrides & { from?: string | Promise<string> } = {}
|
||||
) {
|
||||
const recipientChainId = coalesceChainId(recipientChain);
|
||||
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
||||
const v = await bridge.transferTokens(
|
||||
tokenAddress,
|
||||
amount,
|
||||
recipientChain,
|
||||
recipientChainId,
|
||||
recipientAddress,
|
||||
relayerFee,
|
||||
createNonce(),
|
||||
|
@ -96,14 +99,15 @@ export async function transferFromEthNative(
|
|||
tokenBridgeAddress: string,
|
||||
signer: ethers.Signer,
|
||||
amount: ethers.BigNumberish,
|
||||
recipientChain: ChainId,
|
||||
recipientChain: ChainId | ChainId,
|
||||
recipientAddress: Uint8Array,
|
||||
relayerFee: ethers.BigNumberish = 0,
|
||||
overrides: PayableOverrides & { from?: string | Promise<string> } = {}
|
||||
) {
|
||||
const recipientChainId = coalesceChainId(recipientChain);
|
||||
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
||||
const v = await bridge.wrapAndTransferETH(
|
||||
recipientChain,
|
||||
recipientChainId,
|
||||
recipientAddress,
|
||||
relayerFee,
|
||||
createNonce(),
|
||||
|
@ -121,10 +125,11 @@ export async function transferFromTerra(
|
|||
tokenBridgeAddress: string,
|
||||
tokenAddress: string,
|
||||
amount: string,
|
||||
recipientChain: ChainId,
|
||||
recipientChain: ChainId | ChainName,
|
||||
recipientAddress: Uint8Array,
|
||||
relayerFee: string = "0"
|
||||
) {
|
||||
const recipientChainId = coalesceChainId(recipientChain);
|
||||
const nonce = Math.round(Math.random() * 100000);
|
||||
const isNativeAsset = isNativeDenom(tokenAddress);
|
||||
return isNativeAsset
|
||||
|
@ -150,7 +155,7 @@ export async function transferFromTerra(
|
|||
},
|
||||
},
|
||||
},
|
||||
recipient_chain: recipientChain,
|
||||
recipient_chain: recipientChainId,
|
||||
recipient: Buffer.from(recipientAddress).toString("base64"),
|
||||
fee: relayerFee,
|
||||
nonce: nonce,
|
||||
|
@ -187,7 +192,7 @@ export async function transferFromTerra(
|
|||
},
|
||||
},
|
||||
},
|
||||
recipient_chain: recipientChain,
|
||||
recipient_chain: recipientChainId,
|
||||
recipient: Buffer.from(recipientAddress).toString("base64"),
|
||||
fee: relayerFee,
|
||||
nonce: nonce,
|
||||
|
@ -205,7 +210,7 @@ export async function transferNativeSol(
|
|||
payerAddress: string,
|
||||
amount: BigInt,
|
||||
targetAddress: Uint8Array,
|
||||
targetChain: ChainId,
|
||||
targetChain: ChainId | ChainName,
|
||||
relayerFee: BigInt = BigInt(0)
|
||||
) {
|
||||
//https://github.com/solana-labs/solana-program-library/blob/master/token/js/client/token.js
|
||||
|
@ -268,7 +273,7 @@ export async function transferNativeSol(
|
|||
amount,
|
||||
relayerFee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -304,12 +309,15 @@ export async function transferFromSolana(
|
|||
mintAddress: string,
|
||||
amount: BigInt,
|
||||
targetAddress: Uint8Array,
|
||||
targetChain: ChainId,
|
||||
targetChain: ChainId | ChainName,
|
||||
originAddress?: Uint8Array,
|
||||
originChain?: ChainId,
|
||||
originChain?: ChainId | ChainName,
|
||||
fromOwnerAddress?: string,
|
||||
relayerFee: BigInt = BigInt(0)
|
||||
) {
|
||||
const originChainId: ChainId | undefined = originChain
|
||||
? coalesceChainId(originChain)
|
||||
: undefined;
|
||||
const nonce = createNonce().readUInt32LE(0);
|
||||
const transferIx = await getBridgeFeeIx(
|
||||
connection,
|
||||
|
@ -331,7 +339,7 @@ export async function transferFromSolana(
|
|||
);
|
||||
let messageKey = Keypair.generate();
|
||||
const isSolanaNative =
|
||||
originChain === undefined || originChain === CHAIN_ID_SOLANA;
|
||||
originChainId === undefined || originChainId === CHAIN_ID_SOLANA;
|
||||
if (!isSolanaNative && !originAddress) {
|
||||
throw new Error("originAddress is required when specifying originChain");
|
||||
}
|
||||
|
@ -348,7 +356,7 @@ export async function transferFromSolana(
|
|||
amount,
|
||||
relayerFee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
: transfer_wrapped_ix(
|
||||
tokenBridgeAddress,
|
||||
|
@ -357,13 +365,13 @@ export async function transferFromSolana(
|
|||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
fromOwnerAddress || payerAddress,
|
||||
originChain as number, // checked by isSolanaNative
|
||||
originChainId as number, // checked by isSolanaNative
|
||||
originAddress as Uint8Array, // checked by throw
|
||||
nonce,
|
||||
amount,
|
||||
relayerFee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
);
|
||||
const transaction = new SolanaTransaction().add(transferIx, approvalIx, ix);
|
||||
|
@ -395,9 +403,10 @@ export async function transferFromAlgorand(
|
|||
assetId: bigint,
|
||||
qty: bigint,
|
||||
receiver: string,
|
||||
chain: ChainId,
|
||||
chain: ChainId | ChainName,
|
||||
fee: bigint
|
||||
): Promise<TransactionSignerPair[]> {
|
||||
const recipientChainId = coalesceChainId(chain);
|
||||
const tokenAddr: string = getApplicationAddress(tokenBridgeId);
|
||||
const applAddr: string = getEmitterAddressAlgorand(tokenBridgeId);
|
||||
const txs: TransactionSignerPair[] = [];
|
||||
|
@ -512,7 +521,7 @@ export async function transferFromAlgorand(
|
|||
bigIntToBytes(assetId, 8),
|
||||
bigIntToBytes(qty, 8),
|
||||
hexToUint8Array(receiver),
|
||||
bigIntToBytes(chain, 8),
|
||||
bigIntToBytes(recipientChainId, 8),
|
||||
bigIntToBytes(fee, 8),
|
||||
];
|
||||
let acTxn = makeApplicationCallTxnFromObject({
|
||||
|
|
|
@ -3,98 +3,172 @@ import { PublicKey } from "@solana/web3.js";
|
|||
import { hexValue, hexZeroPad, stripZeros } from "ethers/lib/utils";
|
||||
import {
|
||||
hexToNativeAssetStringAlgorand,
|
||||
hexToNativeStringAlgorand,
|
||||
nativeStringToHexAlgorand,
|
||||
uint8ArrayToNativeStringAlgorand,
|
||||
} from "../algorand";
|
||||
import { canonicalAddress, humanAddress, isNativeDenom } from "../terra";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ACALA,
|
||||
ChainName,
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_AURORA,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_CELO,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_ETHEREUM_ROPSTEN,
|
||||
CHAIN_ID_FANTOM,
|
||||
CHAIN_ID_KARURA,
|
||||
CHAIN_ID_KLAYTN,
|
||||
CHAIN_ID_NEAR,
|
||||
CHAIN_ID_OASIS,
|
||||
CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_UNSET,
|
||||
coalesceChainId,
|
||||
isEVMChain,
|
||||
} from "./consts";
|
||||
|
||||
export const isEVMChain = (chainId: ChainId) => {
|
||||
return (
|
||||
chainId === CHAIN_ID_ETH ||
|
||||
chainId === CHAIN_ID_BSC ||
|
||||
chainId === CHAIN_ID_ETHEREUM_ROPSTEN ||
|
||||
chainId === CHAIN_ID_AVAX ||
|
||||
chainId === CHAIN_ID_POLYGON ||
|
||||
chainId === CHAIN_ID_OASIS ||
|
||||
chainId === CHAIN_ID_AURORA ||
|
||||
chainId === CHAIN_ID_FANTOM ||
|
||||
chainId === CHAIN_ID_KARURA ||
|
||||
chainId === CHAIN_ID_ACALA ||
|
||||
chainId === CHAIN_ID_KLAYTN ||
|
||||
chainId === CHAIN_ID_CELO
|
||||
);
|
||||
/**
|
||||
*
|
||||
* Returns true iff the hex string represents a native Terra denom.
|
||||
*
|
||||
* Native assets on terra don't have an associated smart contract address, just
|
||||
* like eth isn't an ERC-20 contract on Ethereum.
|
||||
*
|
||||
* The difference is that the EVM implementations of Portal don't support eth
|
||||
* directly, and instead require swapping to an ERC-20 wrapped eth (WETH)
|
||||
* contract first.
|
||||
*
|
||||
* The Terra implementation instead supports Terra-native denoms without
|
||||
* wrapping to CW-20 token first. As these denoms don't have an address, they
|
||||
* are encoded in the Portal payloads by the setting the first byte to 1. This
|
||||
* encoding is safe, because the first 12 bytes of the 32-byte wormhole address
|
||||
* space are not used on Terra otherwise, as cosmos addresses are 20 bytes wide.
|
||||
*/
|
||||
export const isHexNativeTerra = (h: string): boolean => h.startsWith("01");
|
||||
|
||||
export const nativeTerraHexToDenom = (h: string): string =>
|
||||
Buffer.from(stripZeros(hexToUint8Array(h.substr(2)))).toString("ascii");
|
||||
|
||||
export const uint8ArrayToHex = (a: Uint8Array): string =>
|
||||
Buffer.from(a).toString("hex");
|
||||
|
||||
export const hexToUint8Array = (h: string): Uint8Array =>
|
||||
new Uint8Array(Buffer.from(h, "hex"));
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a wormhole's 32-byte array representation into a chain's
|
||||
* native string representation.
|
||||
*
|
||||
* @throws if address is not the right length for the given chain
|
||||
*/
|
||||
|
||||
export const tryUint8ArrayToNative = (
|
||||
a: Uint8Array,
|
||||
chain: ChainId | ChainName
|
||||
): string => {
|
||||
const chainId = coalesceChainId(chain);
|
||||
if (isEVMChain(chainId)) {
|
||||
return hexZeroPad(hexValue(a), 20);
|
||||
} else if (chainId === CHAIN_ID_SOLANA) {
|
||||
return new PublicKey(a).toString();
|
||||
} else if (chainId === CHAIN_ID_TERRA) {
|
||||
const h = uint8ArrayToHex(a);
|
||||
if (isHexNativeTerra(h)) {
|
||||
return nativeTerraHexToDenom(h);
|
||||
} else {
|
||||
return humanAddress(a.slice(-20)); // terra expects 20 bytes, not 32
|
||||
}
|
||||
} else if (chainId === CHAIN_ID_ALGORAND) {
|
||||
return uint8ArrayToNativeStringAlgorand(a);
|
||||
} else if (chainId === CHAIN_ID_NEAR) {
|
||||
throw Error("uint8ArrayToNative: Near not supported yet.");
|
||||
} else if (chainId === CHAIN_ID_UNSET) {
|
||||
throw Error("uint8ArrayToNative: Chain id unset");
|
||||
} else {
|
||||
// This case is never reached
|
||||
const _: never = chainId;
|
||||
throw Error("Don't know how to convert address for chain " + chainId);
|
||||
}
|
||||
};
|
||||
|
||||
export const isHexNativeTerra = (h: string) => h.startsWith("01");
|
||||
export const nativeTerraHexToDenom = (h: string) =>
|
||||
Buffer.from(stripZeros(hexToUint8Array(h.substr(2)))).toString("ascii");
|
||||
export const uint8ArrayToHex = (a: Uint8Array) =>
|
||||
Buffer.from(a).toString("hex");
|
||||
export const hexToUint8Array = (h: string) =>
|
||||
new Uint8Array(Buffer.from(h, "hex"));
|
||||
export const hexToNativeString = (h: string | undefined, c: ChainId) => {
|
||||
/**
|
||||
*
|
||||
* Convert an address in a wormhole's 32-byte hex representation into a chain's native
|
||||
* string representation.
|
||||
*
|
||||
* @throws if address is not the right length for the given chain
|
||||
*/
|
||||
export const tryHexToNativeAssetString = (h: string, c: ChainId): string =>
|
||||
c === CHAIN_ID_ALGORAND
|
||||
? // Algorand assets are represented by their asset ids, not an address
|
||||
hexToNativeAssetStringAlgorand(h)
|
||||
: tryHexToNativeString(h, c);
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a wormhole's 32-byte hex representation into a chain's native
|
||||
* string representation.
|
||||
*
|
||||
* @deprecated since 0.3.0, use [[tryHexToNativeString]] instead.
|
||||
*/
|
||||
export const hexToNativeAssetString = (
|
||||
h: string | undefined,
|
||||
c: ChainId
|
||||
): string | undefined => {
|
||||
if (!h) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return !h
|
||||
? undefined
|
||||
: c === CHAIN_ID_SOLANA
|
||||
? new PublicKey(hexToUint8Array(h)).toString()
|
||||
: isEVMChain(c)
|
||||
? hexZeroPad(hexValue(hexToUint8Array(h)), 20)
|
||||
: c === CHAIN_ID_TERRA
|
||||
? isHexNativeTerra(h)
|
||||
? nativeTerraHexToDenom(h)
|
||||
: humanAddress(hexToUint8Array(h.substr(24))) // terra expects 20 bytes, not 32
|
||||
: c === CHAIN_ID_ALGORAND
|
||||
? hexToNativeStringAlgorand(h)
|
||||
: h;
|
||||
} catch (e) {}
|
||||
return undefined;
|
||||
};
|
||||
export const hexToNativeAssetString = (h: string | undefined, c: ChainId) => {
|
||||
try {
|
||||
return !h
|
||||
? undefined
|
||||
: // Algorand assets are represented by their asset ids, not an address
|
||||
c === CHAIN_ID_ALGORAND
|
||||
? hexToNativeAssetStringAlgorand(h)
|
||||
: hexToNativeString(h, c);
|
||||
return tryHexToNativeAssetString(h, c);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const nativeToHexString = (
|
||||
address: string | undefined,
|
||||
chain: ChainId
|
||||
) => {
|
||||
if (!address || !chain) {
|
||||
return null;
|
||||
/**
|
||||
*
|
||||
* Convert an address in a wormhole's 32-byte hex representation into a chain's native
|
||||
* string representation.
|
||||
*
|
||||
* @throws if address is not the right length for the given chain
|
||||
*/
|
||||
export const tryHexToNativeString = (
|
||||
h: string,
|
||||
c: ChainId | ChainName
|
||||
): string => tryUint8ArrayToNative(hexToUint8Array(h), c);
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a wormhole's 32-byte hex representation into a chain's native
|
||||
* string representation.
|
||||
*
|
||||
* @deprecated since 0.3.0, use [[tryHexToNativeString]] instead.
|
||||
*/
|
||||
export const hexToNativeString = (
|
||||
h: string | undefined,
|
||||
c: ChainId | ChainName
|
||||
): string | undefined => {
|
||||
if (!h) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isEVMChain(chain)) {
|
||||
try {
|
||||
return tryHexToNativeString(h, c);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a chain's native representation into a 32-byte hex string
|
||||
* understood by wormhole.
|
||||
*
|
||||
* @throws if address is a malformed string for the given chain id
|
||||
*/
|
||||
export const tryNativeToHexString = (
|
||||
address: string,
|
||||
chain: ChainId | ChainName
|
||||
): string => {
|
||||
const chainId = coalesceChainId(chain);
|
||||
if (isEVMChain(chainId)) {
|
||||
return uint8ArrayToHex(zeroPad(arrayify(address), 32));
|
||||
} else if (chain === CHAIN_ID_SOLANA) {
|
||||
} else if (chainId === CHAIN_ID_SOLANA) {
|
||||
return uint8ArrayToHex(zeroPad(new PublicKey(address).toBytes(), 32));
|
||||
} else if (chain === CHAIN_ID_TERRA) {
|
||||
} else if (chainId === CHAIN_ID_TERRA) {
|
||||
if (isNativeDenom(address)) {
|
||||
return (
|
||||
"01" +
|
||||
|
@ -105,13 +179,60 @@ export const nativeToHexString = (
|
|||
} else {
|
||||
return uint8ArrayToHex(zeroPad(canonicalAddress(address), 32));
|
||||
}
|
||||
} else if (chain === CHAIN_ID_ALGORAND) {
|
||||
} else if (chainId === CHAIN_ID_ALGORAND) {
|
||||
return nativeStringToHexAlgorand(address);
|
||||
} else if (chainId === CHAIN_ID_NEAR) {
|
||||
throw Error("hexToNativeString: Near not supported yet.");
|
||||
} else if (chainId === CHAIN_ID_UNSET) {
|
||||
throw Error("hexToNativeString: Chain id unset");
|
||||
} else {
|
||||
return null;
|
||||
// If this case is reached
|
||||
const _: never = chainId;
|
||||
throw Error("Don't know how to convert address from chain " + chainId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a chain's native representation into a 32-byte hex string
|
||||
* understood by wormhole.
|
||||
*
|
||||
* @deprecated since 0.3.0, use [[tryNativeToHexString]] instead.
|
||||
* @throws if address is a malformed string for the given chain id
|
||||
*/
|
||||
export const nativeToHexString = (
|
||||
address: string | undefined,
|
||||
chain: ChainId | ChainName
|
||||
): string | null => {
|
||||
if (!address) {
|
||||
return null;
|
||||
}
|
||||
return tryNativeToHexString(address, chain);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a chain's native representation into a 32-byte array
|
||||
* understood by wormhole.
|
||||
*
|
||||
* @throws if address is a malformed string for the given chain id
|
||||
*/
|
||||
export function tryNativeToUint8Array(
|
||||
address: string,
|
||||
chain: ChainId | ChainName
|
||||
): Uint8Array {
|
||||
const chainId = coalesceChainId(chain);
|
||||
return hexToUint8Array(tryNativeToHexString(address, chainId));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert an address in a chain's native representation into a 32-byte hex string
|
||||
* understood by wormhole.
|
||||
*
|
||||
* @deprecated since 0.3.0, use [[tryUint8ArrayToNative]] instead.
|
||||
* @throws if address is a malformed string for the given chain id
|
||||
*/
|
||||
export const uint8ArrayToNative = (a: Uint8Array, chainId: ChainId) =>
|
||||
hexToNativeString(uint8ArrayToHex(a), chainId);
|
||||
|
||||
|
|
|
@ -1,20 +1,501 @@
|
|||
export type ChainId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 10001;
|
||||
export const CHAIN_ID_SOLANA: ChainId = 1;
|
||||
export const CHAIN_ID_ETH: ChainId = 2;
|
||||
export const CHAIN_ID_TERRA: ChainId = 3;
|
||||
export const CHAIN_ID_BSC: ChainId = 4;
|
||||
export const CHAIN_ID_POLYGON: ChainId = 5;
|
||||
export const CHAIN_ID_AVAX: ChainId = 6;
|
||||
export const CHAIN_ID_OASIS: ChainId = 7;
|
||||
export const CHAIN_ID_ALGORAND: ChainId = 8;
|
||||
export const CHAIN_ID_AURORA: ChainId = 9;
|
||||
export const CHAIN_ID_FANTOM: ChainId = 10;
|
||||
export const CHAIN_ID_KARURA: ChainId = 11;
|
||||
export const CHAIN_ID_ACALA: ChainId = 12;
|
||||
export const CHAIN_ID_KLAYTN: ChainId = 13;
|
||||
export const CHAIN_ID_CELO: ChainId = 14;
|
||||
export const CHAIN_ID_NEAR: ChainId = 15;
|
||||
export const CHAIN_ID_ETHEREUM_ROPSTEN: ChainId = 10001;
|
||||
export const CHAINS = {
|
||||
unset: 0,
|
||||
solana: 1,
|
||||
ethereum: 2,
|
||||
terra: 3,
|
||||
bsc: 4,
|
||||
polygon: 5,
|
||||
avalanche: 6,
|
||||
oasis: 7,
|
||||
algorand: 8,
|
||||
aurora: 9,
|
||||
fantom: 10,
|
||||
karura: 11,
|
||||
acala: 12,
|
||||
klaytn: 13,
|
||||
celo: 14,
|
||||
near: 15,
|
||||
ropsten: 10001,
|
||||
} as const;
|
||||
|
||||
export type ChainName = keyof typeof CHAINS;
|
||||
export type ChainId = typeof CHAINS[ChainName];
|
||||
|
||||
/**
|
||||
*
|
||||
* All the EVM-based chain names that Wormhole supports
|
||||
*/
|
||||
export type EVMChainName =
|
||||
| "ethereum"
|
||||
| "bsc"
|
||||
| "polygon"
|
||||
| "avalanche"
|
||||
| "oasis"
|
||||
| "aurora"
|
||||
| "fantom"
|
||||
| "karura"
|
||||
| "acala"
|
||||
| "klaytn"
|
||||
| "celo"
|
||||
| "ropsten";
|
||||
|
||||
export type Contracts = {
|
||||
core: string | undefined;
|
||||
token_bridge: string | undefined;
|
||||
nft_bridge: string | undefined;
|
||||
};
|
||||
|
||||
export type ChainContracts = {
|
||||
[chain in ChainName]: Contracts;
|
||||
};
|
||||
|
||||
const MAINNET = {
|
||||
unset: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
solana: {
|
||||
core: "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth",
|
||||
token_bridge: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb",
|
||||
nft_bridge: "WnFt12ZrnzZrFZkt2xsNsaNWoQribnuQ5B5FrDbwDhD",
|
||||
},
|
||||
ethereum: {
|
||||
core: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
|
||||
token_bridge: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
nft_bridge: "0x6FFd7EdE62328b3Af38FCD61461Bbfc52F5651fE",
|
||||
},
|
||||
terra: {
|
||||
core: "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5",
|
||||
token_bridge: "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf",
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
bsc: {
|
||||
core: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
|
||||
token_bridge: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
||||
nft_bridge: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
|
||||
},
|
||||
polygon: {
|
||||
core: "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7",
|
||||
token_bridge: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
|
||||
nft_bridge: "0x90BBd86a6Fe93D3bc3ed6335935447E75fAb7fCf",
|
||||
},
|
||||
avalanche: {
|
||||
core: "0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c",
|
||||
token_bridge: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
|
||||
nft_bridge: "0xf7B6737Ca9c4e08aE573F75A97B73D7a813f5De5",
|
||||
},
|
||||
oasis: {
|
||||
core: "0xfE8cD454b4A1CA468B57D79c0cc77Ef5B6f64585",
|
||||
token_bridge: "0x5848C791e09901b40A9Ef749f2a6735b418d7564",
|
||||
nft_bridge: "0x04952D522Ff217f40B5Ef3cbF659EcA7b952a6c1",
|
||||
},
|
||||
algorand: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
aurora: {
|
||||
core: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
|
||||
token_bridge: "0x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F",
|
||||
nft_bridge: "0x6dcC0484472523ed9Cdc017F711Bcbf909789284",
|
||||
},
|
||||
fantom: {
|
||||
core: "0x126783A6Cb203a3E35344528B26ca3a0489a1485",
|
||||
token_bridge: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2",
|
||||
nft_bridge: "0xA9c7119aBDa80d4a4E0C06C8F4d8cF5893234535",
|
||||
},
|
||||
karura: {
|
||||
core: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
|
||||
token_bridge: "0xae9d7fe007b3327AA64A32824Aaac52C42a6E624",
|
||||
nft_bridge: "0xb91e3638F82A1fACb28690b37e3aAE45d2c33808",
|
||||
},
|
||||
acala: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
klaytn: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
celo: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
near: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
ropsten: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const TESTNET = {
|
||||
unset: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
solana: {
|
||||
core: "3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5",
|
||||
token_bridge: "DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe",
|
||||
nft_bridge: "2rHhojZ7hpu1zA91nvZmT8TqWWvMcKmmNBCr2mKTtMq4",
|
||||
},
|
||||
terra: {
|
||||
core: "terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v",
|
||||
token_bridge: "terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a",
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
ethereum: {
|
||||
core: "0x706abc4E45D419950511e474C7B9Ed348A4a716c",
|
||||
token_bridge: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7",
|
||||
nft_bridge: "0xD8E4C2DbDd2e2bd8F1336EA691dBFF6952B1a6eB",
|
||||
},
|
||||
bsc: {
|
||||
core: "0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D",
|
||||
token_bridge: "0x9dcF9D205C9De35334D646BeE44b2D2859712A09",
|
||||
nft_bridge: "0xcD16E5613EF35599dc82B24Cb45B5A93D779f1EE",
|
||||
},
|
||||
polygon: {
|
||||
core: "0x0CBE91CF822c73C2315FB05100C2F714765d5c20",
|
||||
token_bridge: "0x377D55a7928c046E18eEbb61977e714d2a76472a",
|
||||
nft_bridge: "0x51a02d0dcb5e52F5b92bdAA38FA013C91c7309A9",
|
||||
},
|
||||
avalanche: {
|
||||
core: "0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C",
|
||||
token_bridge: "0x61E44E506Ca5659E6c0bba9b678586fA2d729756",
|
||||
nft_bridge: "0xD601BAf2EEE3C028344471684F6b27E789D9075D",
|
||||
},
|
||||
oasis: {
|
||||
core: "0xc1C338397ffA53a2Eb12A7038b4eeb34791F8aCb",
|
||||
token_bridge: "0x88d8004A9BdbfD9D28090A02010C19897a29605c",
|
||||
nft_bridge: "0xC5c25B41AB0b797571620F5204Afa116A44c0ebA",
|
||||
},
|
||||
algorand: {
|
||||
core: "86525623",
|
||||
token_bridge: "86525641",
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
aurora: {
|
||||
core: "0xBd07292de7b505a4E803CEe286184f7Acf908F5e",
|
||||
token_bridge: "0xD05eD3ad637b890D68a854d607eEAF11aF456fba",
|
||||
nft_bridge: "0x8F399607E9BA2405D87F5f3e1B78D950b44b2e24",
|
||||
},
|
||||
fantom: {
|
||||
core: "0x1BB3B4119b7BA9dfad76B0545fb3F531383c3bB7",
|
||||
token_bridge: "0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8",
|
||||
nft_bridge: "0x63eD9318628D26BdCB15df58B53BB27231D1B227",
|
||||
},
|
||||
karura: {
|
||||
core: "0xE4eacc10990ba3308DdCC72d985f2a27D20c7d03",
|
||||
token_bridge: "0xd11De1f930eA1F7Dd0290Fe3a2e35b9C91AEFb37",
|
||||
nft_bridge: "0x0A693c2D594292B6Eb89Cb50EFe4B0b63Dd2760D",
|
||||
},
|
||||
acala: {
|
||||
core: "0x4377B49d559c0a9466477195C6AdC3D433e265c0",
|
||||
token_bridge: "0xebA00cbe08992EdD08ed7793E07ad6063c807004",
|
||||
nft_bridge: "0x96f1335e0AcAB3cfd9899B30b2374e25a2148a6E",
|
||||
},
|
||||
klaytn: {
|
||||
core: "0x1830CC6eE66c84D2F177B94D544967c774E624cA",
|
||||
token_bridge: "0xC7A13BE098720840dEa132D860fDfa030884b09A",
|
||||
nft_bridge: "0x94c994fC51c13101062958b567e743f1a04432dE",
|
||||
},
|
||||
celo: {
|
||||
core: "0x88505117CA88e7dd2eC6EA1E13f0948db2D50D56",
|
||||
token_bridge: "0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153",
|
||||
nft_bridge: "0xaCD8190F647a31E56A656748bC30F69259f245Db",
|
||||
},
|
||||
near: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
ropsten: {
|
||||
core: "0x210c5F5e2AF958B4defFe715Dc621b7a3BA888c5",
|
||||
token_bridge: "0xF174F9A837536C449321df1Ca093Bb96948D5386",
|
||||
nft_bridge: "0x2b048Da40f69c8dc386a56705915f8E966fe1eba",
|
||||
},
|
||||
};
|
||||
|
||||
const DEVNET = {
|
||||
unset: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
solana: {
|
||||
core: "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o",
|
||||
token_bridge: "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE",
|
||||
nft_bridge: "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA",
|
||||
},
|
||||
terra: {
|
||||
core: "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
|
||||
token_bridge: "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4",
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
ethereum: {
|
||||
core: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||
token_bridge: "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||
nft_bridge: "0x26b4afb60d6c903165150c6f0aa14f8016be4aec",
|
||||
},
|
||||
bsc: {
|
||||
core: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||
token_bridge: "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||
nft_bridge: "0x26b4afb60d6c903165150c6f0aa14f8016be4aec",
|
||||
},
|
||||
polygon: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
avalanche: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
oasis: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
algorand: {
|
||||
core: "4",
|
||||
token_bridge: "6",
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
aurora: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
fantom: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
karura: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
acala: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
klaytn: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
celo: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
near: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
ropsten: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* If you get a type error here, it means that a chain you just added does not
|
||||
* have an entry in TESTNET.
|
||||
* This is implemented as an ad-hoc type assertion instead of a type annotation
|
||||
* on TESTNET so that e.g.
|
||||
*
|
||||
* ```typescript
|
||||
* TESTNET['solana'].core
|
||||
* ```
|
||||
* has type 'string' instead of 'string | undefined'.
|
||||
*
|
||||
* (Do not delete this declaration!)
|
||||
*/
|
||||
const isTestnetContracts: ChainContracts = TESTNET;
|
||||
|
||||
/**
|
||||
*
|
||||
* See [[isTestnetContracts]]
|
||||
*/
|
||||
const isMainnetContracts: ChainContracts = MAINNET;
|
||||
|
||||
/**
|
||||
*
|
||||
* See [[isTestnetContracts]]
|
||||
*/
|
||||
const isDevnetContracts: ChainContracts = DEVNET;
|
||||
|
||||
/**
|
||||
*
|
||||
* Contracts addresses on testnet and mainnet
|
||||
*/
|
||||
export const CONTRACTS = { MAINNET, TESTNET, DEVNET };
|
||||
|
||||
// We don't specify the types of the below consts to be [[ChainId]]. This way,
|
||||
// the inferred type will be a singleton (or literal) type, which is more precise and allows
|
||||
// typescript to perform context-sensitive narrowing when checking against them.
|
||||
// See the [[isEVMChain]] for an example.
|
||||
export const CHAIN_ID_UNSET = CHAINS["unset"];
|
||||
export const CHAIN_ID_SOLANA = CHAINS["solana"];
|
||||
export const CHAIN_ID_ETH = CHAINS["ethereum"];
|
||||
export const CHAIN_ID_TERRA = CHAINS["terra"];
|
||||
export const CHAIN_ID_BSC = CHAINS["bsc"];
|
||||
export const CHAIN_ID_POLYGON = CHAINS["polygon"];
|
||||
export const CHAIN_ID_AVAX = CHAINS["avalanche"];
|
||||
export const CHAIN_ID_OASIS = CHAINS["oasis"];
|
||||
export const CHAIN_ID_ALGORAND = CHAINS["algorand"];
|
||||
export const CHAIN_ID_AURORA = CHAINS["aurora"];
|
||||
export const CHAIN_ID_FANTOM = CHAINS["fantom"];
|
||||
export const CHAIN_ID_KARURA = CHAINS["karura"];
|
||||
export const CHAIN_ID_ACALA = CHAINS["acala"];
|
||||
export const CHAIN_ID_KLAYTN = CHAINS["klaytn"];
|
||||
export const CHAIN_ID_CELO = CHAINS["celo"];
|
||||
export const CHAIN_ID_NEAR = CHAINS["near"];
|
||||
export const CHAIN_ID_ETHEREUM_ROPSTEN = CHAINS["ropsten"];
|
||||
|
||||
// This inverts the [[CHAINS]] object so that we can look up a chain by id
|
||||
export type ChainIdToName = {
|
||||
-readonly [key in keyof typeof CHAINS as typeof CHAINS[key]]: key;
|
||||
};
|
||||
export const CHAIN_ID_TO_NAME: ChainIdToName = Object.entries(CHAINS).reduce(
|
||||
(obj, [name, id]) => {
|
||||
obj[id] = name;
|
||||
return obj;
|
||||
},
|
||||
{} as any
|
||||
) as ChainIdToName;
|
||||
|
||||
/**
|
||||
*
|
||||
* All the EVM-based chain ids that Wormhole supports
|
||||
*/
|
||||
export type EVMChainId = typeof CHAINS[EVMChainName];
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns true when called with a valid chain, and narrows the type in the
|
||||
* "true" branch to [[ChainId]] or [[ChainName]] thanks to the type predicate in
|
||||
* the return type.
|
||||
*
|
||||
* A typical use-case might look like
|
||||
* ```typescript
|
||||
* foo = isChain(c) ? doSomethingWithChainId(c) : handleInvalidCase()
|
||||
* ```
|
||||
*/
|
||||
export function isChain(chain: number | string): chain is ChainId | ChainName {
|
||||
if (typeof chain === "number") {
|
||||
return chain in CHAIN_ID_TO_NAME;
|
||||
} else {
|
||||
return chain in CHAINS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Asserts that the given number or string is a valid chain, and throws otherwise.
|
||||
* After calling this function, the type of chain will be narrowed to
|
||||
* [[ChainId]] or [[ChainName]] thanks to the type assertion in the return type.
|
||||
*
|
||||
* A typical use-case might look like
|
||||
* ```typescript
|
||||
* // c has type 'string'
|
||||
* assertChain(c)
|
||||
* // c now has type 'ChainName'
|
||||
* ```
|
||||
*/
|
||||
export function assertChain(
|
||||
chain: number | string
|
||||
): asserts chain is ChainId | ChainName {
|
||||
if (!isChain(chain)) {
|
||||
if (typeof chain === "number") {
|
||||
throw Error(`Unknown chain id: ${chain}`);
|
||||
} else {
|
||||
throw Error(`Unknown chain: ${chain}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function toChainId(chainName: ChainName): ChainId {
|
||||
return CHAINS[chainName];
|
||||
}
|
||||
|
||||
export function toChainName(chainId: ChainId): ChainName {
|
||||
return CHAIN_ID_TO_NAME[chainId];
|
||||
}
|
||||
|
||||
export function coalesceChainId(chain: ChainId | ChainName): ChainId {
|
||||
// this is written in a way that for invalid inputs (coming from vanilla
|
||||
// javascript or someone doing type casting) it will always return undefined.
|
||||
return typeof chain === "number" && isChain(chain) ? chain : toChainId(chain);
|
||||
}
|
||||
|
||||
export function coalesceChainName(chain: ChainId | ChainName): ChainName {
|
||||
// this is written in a way that for invalid inputs (coming from vanilla
|
||||
// javascript or someone doing type casting) it will always return undefined.
|
||||
return toChainName(coalesceChainId(chain));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns true when called with an [[EVMChainId]] or [[EVMChainName]], and false otherwise.
|
||||
* Importantly, after running this check, the chain's type will be narrowed to
|
||||
* either the EVM subset, or the non-EVM subset thanks to the type predicate in
|
||||
* the return type.
|
||||
*/
|
||||
export function isEVMChain(
|
||||
chain: ChainId | ChainName
|
||||
): chain is EVMChainId | EVMChainName {
|
||||
let chainId = coalesceChainId(chain);
|
||||
if (
|
||||
chainId === CHAIN_ID_ETH ||
|
||||
chainId === CHAIN_ID_BSC ||
|
||||
chainId === CHAIN_ID_AVAX ||
|
||||
chainId === CHAIN_ID_POLYGON ||
|
||||
chainId === CHAIN_ID_OASIS ||
|
||||
chainId === CHAIN_ID_AURORA ||
|
||||
chainId === CHAIN_ID_FANTOM ||
|
||||
chainId === CHAIN_ID_KARURA ||
|
||||
chainId === CHAIN_ID_ACALA ||
|
||||
chainId === CHAIN_ID_KLAYTN ||
|
||||
chainId === CHAIN_ID_CELO ||
|
||||
chainId === CHAIN_ID_ETHEREUM_ROPSTEN
|
||||
) {
|
||||
return isEVM(chainId);
|
||||
} else {
|
||||
return notEVM(chainId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Asserts that the given chain id or chain name is an EVM chain, and throws otherwise.
|
||||
* After calling this function, the type of chain will be narrowed to
|
||||
* [[EVMChainId]] or [[EVMChainName]] thanks to the type assertion in the return type.
|
||||
*
|
||||
*/
|
||||
export function assertEVMChain(
|
||||
chain: ChainId | ChainName
|
||||
): asserts chain is EVMChainId | EVMChainName {
|
||||
if (!isEVMChain(chain)) {
|
||||
throw Error(`Expected an EVM chain, but ${chain} is not`);
|
||||
}
|
||||
}
|
||||
|
||||
export const WSOL_ADDRESS = "So11111111111111111111111111111111111111112";
|
||||
export const WSOL_DECIMALS = 9;
|
||||
|
@ -22,3 +503,56 @@ export const MAX_VAA_DECIMALS = 8;
|
|||
|
||||
export const TERRA_REDEEMED_CHECK_WALLET_ADDRESS =
|
||||
"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Utilities
|
||||
|
||||
/**
|
||||
* The [[isEVM]] and [[notEVM]] functions improve type-safety in [[isEVMChain]].
|
||||
*
|
||||
* As it turns out, typescript type predicates are unsound on their own,
|
||||
* allowing us to write something like this:
|
||||
*
|
||||
* ```typescript
|
||||
* function unsafeCoerce(n: number): n is 1 {
|
||||
* return true
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* which is completely bogus. This happens presumably because the typescript
|
||||
* authors think of the type predicate mechanism as an escape hatch mechanism.
|
||||
* We want a more principled function though, that keeps us honest.
|
||||
*
|
||||
* in [[isEVMChain]], checking that disjunctive boolean expression actually
|
||||
* refines the type of chainId in both branches. In the "true" branch,
|
||||
* the type of chainId is narrowed to exactly the EVM chains, so calling
|
||||
* [[isEVM]] on it will typecheck, and similarly the "false" branch for the negation.
|
||||
* However, if we extend the [[EVMChainId]] type with a new EVM chain, this
|
||||
* function will no longer compile until the condition is extended.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns true when called with an [[EVMChainId]] or [[EVMChainName]], and fails to compile
|
||||
* otherwise
|
||||
*/
|
||||
function isEVM(_: EVMChainId | EVMChainName): true {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns false when called with a non-[[EVMChainId]] and non-[[EVMChainName]]
|
||||
* argument, and fails to compile otherwise
|
||||
*/
|
||||
function notEVM<T>(_: T extends EVMChainId | EVMChainName ? never : T): false {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This just serves as a type assertion to ensure that [[EVMChainName]] is a
|
||||
// subset of [[ChainName]], since typescript provides no built-in way to express
|
||||
// this.
|
||||
function evm_chain_subset(e: EVMChainName): ChainName {
|
||||
// will fail to compile if 'e' can't be typed as a [[ChainName]]
|
||||
return e;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import { ChainId } from "./consts";
|
|||
|
||||
export const METADATA_REPLACE = new RegExp("\u0000", "g");
|
||||
|
||||
// TODO: remove `as ChainId` in next minor version as we can't ensure it will match our type definition
|
||||
|
||||
// note: actual first byte is message type
|
||||
// 0 [u8; 32] token_address
|
||||
// 32 u16 token_chain
|
||||
|
|
Loading…
Reference in New Issue