clients/js: Added transfer token command

This commit is contained in:
Kevin Peters 2023-06-08 14:12:48 +00:00 committed by Evan Gray
parent 44273c0641
commit 817f179b34
16 changed files with 772 additions and 90 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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