sdk/js: add Sui support

Co-authored-by: Evan Gray <battledingo@gmail.com>
Co-authored-by: Kevin Peters <kpeters@jumptrading.com>
This commit is contained in:
heyitaki 2023-05-02 16:32:01 +00:00 committed by Evan Gray
parent 760db3c810
commit ed733f8e73
84 changed files with 5340 additions and 740 deletions

View File

@ -73,7 +73,7 @@ ci = cfg.get("ci", False)
algorand = cfg.get("algorand", ci)
near = cfg.get("near", ci)
aptos = cfg.get("aptos", ci)
sui = cfg.get("sui", False)
sui = cfg.get("sui", ci)
evm2 = cfg.get("evm2", ci)
solana = cfg.get("solana", ci)
pythnet = cfg.get("pythnet", False)
@ -187,15 +187,11 @@ def build_node_yaml():
if sui:
container["command"] += [
"--suiRPC",
"http://sui:9002",
# In testnet and mainnet, you will need to also specify the suiPackage argument. In Devnet, we subscribe to
# event traffic purely based on the account since that is the only thing that is deterministic.
# "--suiPackage",
# "0x.....",
"--suiAccount",
"0x2acab6bb0e4722e528291bc6ca4f097e18ce9331",
"http://sui:9000",
"--suiMoveEventType",
"0x9c967677bdc22d2b7217f3e4c62cf74f0ae272cdea5743bb8f28c06d10cdde9f::publish_message::WormholeMessage",
"--suiWS",
"sui:9001",
"sui:9000",
]
if evm2:
@ -428,7 +424,6 @@ if solana or pythnet:
port_forwards = [
port_forward(8899, name = "Solana RPC [:8899]", host = webHost),
port_forward(8900, name = "Solana WS [:8900]", host = webHost),
port_forward(9000, name = "Solana PubSub [:9000]", host = webHost),
],
resource_deps = ["const-gen"],
labels = ["solana"],
@ -716,21 +711,21 @@ if sui:
docker_build(
ref = "sui-node",
context = "sui",
target = "sui",
context = ".",
dockerfile = "sui/Dockerfile",
ignore = ["./sui/sui.log*", "sui/sui.log*", "sui.log.*"],
only = ["Dockerfile", "scripts"],
only = ["./sui", "./clients/js"],
)
k8s_resource(
"sui",
port_forwards = [
port_forward(9001, name = "WS [:9001]", host = webHost),
port_forward(9002, name = "RPC [:9002]", host = webHost),
port_forward(9000, 9000, name = "RPC [:9000]", host = webHost),
port_forward(5003, name = "Faucet [:5003]", host = webHost),
port_forward(9184, name = "Prometheus [:9184]", host = webHost),
],
# resource_deps = ["const-gen"],
resource_deps = ["const-gen"],
labels = ["sui"],
trigger_mode = trigger_mode,
)

View File

@ -0,0 +1,13 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always"
}

18
clients/js/cmds/Yargs.ts Normal file
View File

@ -0,0 +1,18 @@
import yargs from "yargs";
export class Yargs {
yargs: typeof yargs;
constructor(y: typeof yargs) {
this.yargs = y;
}
addCommands = (addCommandsFn: YargsAddCommandsFn) => {
this.yargs = addCommandsFn(this.yargs);
return this;
};
y = () => this.yargs;
}
export type YargsAddCommandsFn = (y: typeof yargs) => typeof yargs;

View File

@ -1,16 +1,15 @@
import { assertChain, CHAIN_ID_APTOS, CHAIN_ID_SOLANA, coalesceChainId, CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { assertChain, CHAIN_ID_APTOS, coalesceChainId, CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { BCS, FaucetClient } from "aptos";
import { spawnSync } from 'child_process';
import fs from 'fs';
import sha3 from 'js-sha3';
import yargs from "yargs";
import { callEntryFunc, deriveResourceAccount, deriveWrappedAssetAddress } from "../aptos";
import { config } from '../config';
import { GOVERNANCE_CHAIN, GOVERNANCE_EMITTER, NAMED_ADDRESSES_OPTIONS, NETWORK_OPTIONS, RPC_OPTIONS } from "../consts";
import { NETWORKS } from "../networks";
import { evm_address, hex } from "../consts";
import { assertNetwork, checkBinary, evm_address, hex } from "../utils";
import { runCommand, validator_args } from '../start-validator';
type Network = "MAINNET" | "TESTNET" | "DEVNET"
import { config } from "../config";
interface Package {
meta_file: string,
@ -23,38 +22,6 @@ interface PackageBCS {
codeHash: Uint8Array
}
const network_options = {
alias: "n",
describe: "network",
type: "string",
choices: ["mainnet", "testnet", "devnet"],
required: true,
} as const;
const rpc_description = {
alias: "r",
describe: "Override default rpc endpoint url",
type: "string",
required: false,
} as const;
const named_addresses = {
describe: "Named addresses in the format addr1=0x0,addr2=0x1,...",
type: "string",
require: false
} as const;
// TODO(csongor): this could be useful elsewhere
function assertNetwork(n: string): asserts n is Network {
if (
n !== "MAINNET" &&
n !== "TESTNET" &&
n !== "DEVNET"
) {
throw Error(`Unknown network: ${n}`);
}
}
exports.command = 'aptos';
exports.desc = 'Aptos utilities';
exports.builder = function(y: typeof yargs) {
@ -64,8 +31,8 @@ exports.builder = function(y: typeof yargs) {
// gets called automatically)
.command("init-token-bridge", "Init token bridge contract", (yargs) => {
return yargs
.option("network", network_options)
.option("rpc", rpc_description)
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
@ -75,8 +42,8 @@ exports.builder = function(y: typeof yargs) {
})
.command("init-wormhole", "Init Wormhole core contract", (yargs) => {
return yargs
.option("network", network_options)
.option("rpc", rpc_description)
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS)
.option("chain-id", {
describe: "Chain id",
type: "number",
@ -86,13 +53,13 @@ exports.builder = function(y: typeof yargs) {
.option("governance-chain-id", {
describe: "Governance chain id",
type: "number",
default: CHAIN_ID_SOLANA,
default: GOVERNANCE_CHAIN,
required: false
})
.option("governance-address", {
describe: "Governance address",
type: "string",
default: "0x0000000000000000000000000000000000000000000000000000000000000004",
default: GOVERNANCE_EMITTER,
required: false
})
.option("guardian-address", {
@ -129,13 +96,13 @@ exports.builder = function(y: typeof yargs) {
.positional("package-dir", {
type: "string"
})
.option("network", network_options)
.option("rpc", rpc_description)
.option("named-addresses", named_addresses)
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS)
.option("named-addresses", NAMED_ADDRESSES_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
checkAptosBinary();
checkBinary("aptos", "aptos");
const p = buildPackage(argv["package-dir"], argv["named-addresses"]);
const b = serializePackage(p);
const rpc = argv.rpc ?? NETWORKS[network]["aptos"].rpc;
@ -150,13 +117,13 @@ exports.builder = function(y: typeof yargs) {
.positional("package-dir", {
type: "string"
})
.option("network", network_options)
.option("rpc", rpc_description)
.option("named-addresses", named_addresses)
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS)
.option("named-addresses", NAMED_ADDRESSES_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
checkAptosBinary();
checkBinary("aptos", "aptos");
const p = buildPackage(argv["package-dir"], argv["named-addresses"]);
const b = serializePackage(p);
const seed = Buffer.from(argv["seed"], "ascii")
@ -185,7 +152,7 @@ exports.builder = function(y: typeof yargs) {
.positional("message", {
type: "string"
})
.option("network", network_options)
.option("network", NETWORK_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
@ -216,7 +183,7 @@ exports.builder = function(y: typeof yargs) {
.positional("origin-address", {
type: "string"
})
.option("network", network_options)
.option("network", NETWORK_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
@ -236,9 +203,9 @@ exports.builder = function(y: typeof yargs) {
.positional("package-dir", {
type: "string"
})
.option("named-addresses", named_addresses)
.option("named-addresses", NAMED_ADDRESSES_OPTIONS)
}, (argv) => {
checkAptosBinary();
checkBinary("aptos", "aptos");
const p = buildPackage(argv["package-dir"], argv["named-addresses"]);
const b = serializePackage(p);
console.log(Buffer.from(b.codeHash).toString("hex"));
@ -256,13 +223,13 @@ exports.builder = function(y: typeof yargs) {
describe: "Address where the wormhole module is deployed",
type: "string",
})
.option("network", network_options)
.option("rpc", rpc_description)
.option("named-addresses", named_addresses)
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS)
.option("named-addresses", NAMED_ADDRESSES_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
checkAptosBinary();
checkBinary("aptos", "aptos");
const p = buildPackage(argv["package-dir"], argv["named-addresses"]);
const b = serializePackage(p);
const rpc = argv.rpc ?? NETWORKS[network]["aptos"].rpc;
@ -290,12 +257,12 @@ exports.builder = function(y: typeof yargs) {
describe: "Address where the wormhole module is deployed",
type: "string",
})
.option("network", network_options)
.option("rpc", rpc_description)
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS)
}, async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
checkAptosBinary();
checkBinary("aptos", "aptos");
const rpc = argv.rpc ?? NETWORKS[network]["aptos"].rpc;
// TODO(csongor): use deployer address from sdk (when it's there)
const hash = await callEntryFunc(
@ -310,7 +277,7 @@ exports.builder = function(y: typeof yargs) {
// TODO - make faucet support testnet in additional to localnet
.command("faucet", "Request money from the faucet for a given account", (yargs) => {
return yargs
.option("rpc", rpc_description)
.option("rpc", RPC_OPTIONS)
.option("faucet", {
alias: "f",
required: false,
@ -357,23 +324,13 @@ exports.builder = function(y: typeof yargs) {
.option("validator-args", validator_args)
}, (argv) => {
const dir = `${config.wormholeDir}/aptos`;
checkAptosBinary();
checkBinary("aptos", "aptos");
const cmd = `cd ${dir} && aptos node run-local-testnet --with-faucet --force-restart --assume-yes`;
runCommand(cmd, argv['validator-args']);
})
.strict().demandCommand();
}
export function checkAptosBinary(): void {
const dir = `${config.wormholeDir}/aptos`;
const aptos = spawnSync("aptos", ["--version"]);
if (aptos.status !== 0) {
console.error("aptos is not installed. Please install aptos and try again.");
console.error(`See ${dir}/README.md for instructions.`);
process.exit(1);
}
}
function buildPackage(dir: string, addrs?: string): Package {
const named_addresses =
addrs

View File

@ -1,6 +1,3 @@
import yargs from "yargs";
import { ethers } from "ethers";
import { NETWORKS } from "../networks";
import {
assertChain,
assertEVMChain,
@ -9,7 +6,10 @@ import {
isEVMChain,
toChainName,
} from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { evm_address } from "../consts";
import { ethers } from "ethers";
import yargs from "yargs";
import { NETWORKS } from "../networks";
import { evm_address } from "../utils";
import { config } from '../config';
import { runCommand, validator_args } from '../start-validator';

View File

@ -1,27 +1,28 @@
import {
CHAINS,
assertChain,
toChainId,
ChainName,
CHAINS,
isCosmWasmChain,
isEVMChain,
toChainId,
} from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { fromBech32, toHex } from "@cosmjs/encoding";
import base58 from "bs58";
import { sha3_256 } from "js-sha3";
import yargs from "yargs";
import { GOVERNANCE_CHAIN, GOVERNANCE_EMITTER } from "../consts";
import { evm_address } from "../utils";
import {
ContractUpgrade,
impossible,
Payload,
PortalRegisterChain,
RecoverChainId,
TokenBridgeAttestMeta,
VAA,
impossible,
serialiseVAA,
sign,
TokenBridgeAttestMeta,
VAA,
} from "../vaa";
import { fromBech32, toHex } from "@cosmjs/encoding";
import base58 from "bs58";
import { evm_address, hex } from "../consts";
function makeVAA(
emitterChain: number,
@ -45,10 +46,6 @@ function makeVAA(
return v;
}
const GOVERNANCE_CHAIN = 1;
const GOVERNANCE_EMITTER =
"0000000000000000000000000000000000000000000000000000000000000004";
exports.command = "generate";
exports.desc = "generate VAAs (devnet and testnet only)";
exports.builder = function (y: typeof yargs) {
@ -286,11 +283,11 @@ function parseAddress(chain: ChainName, address: string): string {
// TODO: is there a better native format for algorand?
return "0x" + evm_address(address);
} else if (chain === "near") {
return "0x" + hex(address).substring(2).padStart(64, "0");
return "0x" + evm_address(address);
} else if (chain === "osmosis") {
throw Error("OSMOSIS is not supported yet");
} else if (chain === "sui") {
throw Error("SUI is not supported yet");
return "0x" + evm_address(address);
} else if (chain === "aptos") {
if (/^(0x)?[0-9a-fA-F]+$/.test(address)) {
return "0x" + evm_address(address);

View File

@ -1,6 +1,6 @@
import yargs from "yargs";
import { ethers } from "ethers";
import { hex } from "../consts";
import yargs from "yargs";
import { hex } from "../utils";
exports.command = "recover <digest> <signature>";
exports.desc = "Recover an address from a signature";

View File

@ -1,13 +1,13 @@
import yargs from "yargs";
import {
CHAINS,
assertChain,
toChainName,
ChainName,
CHAINS,
coalesceChainName,
isEVMChain,
isTerraChain,
coalesceChainName,
toChainName,
} from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import yargs from "yargs";
import * as vaa from "../vaa";
exports.command = "submit <vaa>";
@ -136,7 +136,8 @@ exports.handler = async (argv) => {
} else if (chain === "osmosis") {
throw Error("OSMOSIS is not supported yet");
} else if (chain === "sui") {
throw Error("SUI is not supported yet");
const sui = require("../sui");
await sui.submit(parsed_vaa.payload, buf, network, argv["rpc"]);
} else if (chain === "aptos") {
const aptos = require("../aptos");
await aptos.execute_aptos(
@ -150,6 +151,8 @@ exports.handler = async (argv) => {
throw Error("Wormchain is not supported yet");
} else if (chain === "btc") {
throw Error("btc is not supported yet");
} else if (chain === "sei") {
throw Error("sei 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

View File

@ -0,0 +1,85 @@
import path from "path";
import yargs from "yargs";
import { CONTRACTS, NETWORK_OPTIONS, RPC_OPTIONS } from "../../consts";
import { NETWORKS } from "../../networks";
import { buildCoin, getProvider } from "../../sui";
import { assertNetwork, checkBinary } from "../../utils";
import { YargsAddCommandsFn } from "../Yargs";
export const addBuildCommands: YargsAddCommandsFn = (y: typeof yargs) =>
y.command(
"build-coin",
`Build wrapped coin and dump bytecode.
Example:
worm sui build-coin -d 8 -v V__0_1_1 -n testnet -r "https://fullnode.testnet.sui.io:443"`,
(yargs) =>
yargs
.option("decimals", {
alias: "d",
describe: "Decimals of asset",
required: true,
type: "number",
})
// Can't be called version because of a conflict with the native version option
.option("version-struct", {
alias: "v",
describe: "Version control struct name (e.g. V__0_1_0)",
required: true,
type: "string",
})
.option("network", NETWORK_OPTIONS)
.option("package-path", {
alias: "p",
describe: "Path to coin module",
required: false,
type: "string",
})
.option("wormhole-state", {
alias: "w",
describe: "Wormhole state object ID",
required: false,
type: "string",
})
.option("token-bridge-state", {
alias: "t",
describe: "Token bridge state object ID",
required: false,
type: "string",
})
.option("rpc", RPC_OPTIONS),
async (argv) => {
checkBinary("sui", "sui");
const network = argv.network.toUpperCase();
assertNetwork(network);
const decimals = argv["decimals"];
const version = argv["version-struct"];
const packagePath =
argv["package-path"] ??
path.resolve(__dirname, "../../../../../sui/examples");
const coreBridgeStateObjectId =
argv["wormhole-state"] ?? CONTRACTS[network].sui.core;
const tokenBridgeStateObjectId =
argv["token-bridge-state"] ?? CONTRACTS[network].sui.token_bridge;
const provider = getProvider(
network,
argv.rpc ?? NETWORKS[network].sui.rpc
);
const build = await buildCoin(
provider,
network,
packagePath,
coreBridgeStateObjectId,
tokenBridgeStateObjectId,
version,
decimals
);
console.log(build);
console.log(
"Bytecode hex:",
Buffer.from(build.modules[0], "base64").toString("hex")
);
}
);

View File

@ -0,0 +1,82 @@
import { SuiTransactionBlockResponse } from "@mysten/sui.js";
import fs from "fs";
import yargs from "yargs";
import {
DEBUG_OPTIONS,
NETWORK_OPTIONS,
PRIVATE_KEY_OPTIONS,
RPC_OPTIONS,
} from "../../consts";
import { NETWORKS } from "../../networks";
import {
getProvider,
getSigner,
logCreatedObjects,
logPublishedPackageId,
logTransactionDigest,
logTransactionSender,
publishPackage,
} from "../../sui";
import { Network, assertNetwork, checkBinary } from "../../utils";
import { YargsAddCommandsFn } from "../Yargs";
export const addDeployCommands: YargsAddCommandsFn = (y: typeof yargs) =>
y.command(
"deploy <package-dir>",
"Deploy a Sui package",
(yargs) => {
return yargs
.positional("package-dir", {
type: "string",
})
.option("network", NETWORK_OPTIONS)
.option("debug", DEBUG_OPTIONS)
.option("private-key", PRIVATE_KEY_OPTIONS)
.option("rpc", RPC_OPTIONS);
},
async (argv) => {
checkBinary("sui", "sui");
const packageDir = argv["package-dir"];
const network = argv.network.toUpperCase();
assertNetwork(network);
const debug = argv.debug ?? false;
const privateKey = argv["private-key"];
const rpc = argv.rpc;
const res = await deploy(network, packageDir, rpc, privateKey);
// Dump deployment info to console
logTransactionDigest(res);
logPublishedPackageId(res);
if (debug) {
logTransactionSender(res);
logCreatedObjects(res);
}
}
);
export const deploy = async (
network: Network,
packageDir: string,
rpc?: string,
privateKey?: string
): Promise<SuiTransactionBlockResponse> => {
rpc = rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
const signer = getSigner(provider, network, privateKey);
// Allow absolute paths, otherwise assume relative to directory `worm` command is run from
const dir = packageDir.startsWith("/")
? packageDir
: `${process.cwd()}/${packageDir}`;
const packagePath = dir.endsWith("/") ? dir.slice(0, -1) : dir;
if (!fs.existsSync(packagePath)) {
throw new Error(
`Package directory ${packagePath} does not exist. Make sure to deploy from the correct directory or provide an absolute path.`
);
}
return publishPackage(signer, network, packagePath);
};

View File

@ -0,0 +1,23 @@
import yargs from "yargs";
import { Yargs } from "../Yargs";
import { addBuildCommands } from "./build";
import { addDeployCommands } from "./deploy";
import { addInitCommands } from "./init";
import { addPublishMessageCommands } from "./publish_message";
import { addSetupCommands } from "./setup";
import { addUtilsCommands } from "./utils";
exports.command = "sui";
exports.desc = "Sui utilities";
exports.builder = function (y: typeof yargs) {
return new Yargs(y)
.addCommands(addBuildCommands)
.addCommands(addDeployCommands)
.addCommands(addInitCommands)
.addCommands(addPublishMessageCommands)
.addCommands(addSetupCommands)
.addCommands(addUtilsCommands)
.y()
.strict()
.demandCommand();
};

368
clients/js/cmds/sui/init.ts Normal file
View File

@ -0,0 +1,368 @@
import { SuiTransactionBlockResponse, TransactionBlock } from "@mysten/sui.js";
import yargs from "yargs";
import {
DEBUG_OPTIONS,
GOVERNANCE_CHAIN,
GOVERNANCE_EMITTER,
NETWORK_OPTIONS,
PRIVATE_KEY_OPTIONS,
RPC_OPTIONS,
} from "../../consts";
import { NETWORKS } from "../../networks";
import {
executeTransactionBlock,
getCreatedObjects,
getOwnedObjectId,
getPackageId,
getProvider,
getSigner,
getUpgradeCapObjectId,
isSameType,
logTransactionDigest,
logTransactionSender,
} from "../../sui";
import { Network, assertNetwork } from "../../utils";
import { YargsAddCommandsFn } from "../Yargs";
export const addInitCommands: YargsAddCommandsFn = (y: typeof yargs) =>
y
.command(
"init-example-message-app",
"Initialize example core message app",
(yargs) => {
return yargs
.option("network", NETWORK_OPTIONS)
.option("package-id", {
alias: "p",
describe: "Example app package ID",
required: true,
type: "string",
})
.option("wormhole-state", {
alias: "w",
describe: "Wormhole state object ID",
required: true,
type: "string",
})
.option("private-key", PRIVATE_KEY_OPTIONS)
.option("rpc", RPC_OPTIONS);
},
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const packageId = argv["package-id"];
const wormholeStateObjectId = argv["wormhole-state"];
const privateKey = argv["private-key"];
const rpc = argv.rpc;
const res = await initExampleApp(
network,
packageId,
wormholeStateObjectId,
rpc,
privateKey
);
logTransactionDigest(res);
logTransactionSender(res);
console.log(
"Example app state object ID",
getCreatedObjects(res).find((e) =>
isSameType(e.type, `${packageId}::sender::State`)
).objectId
);
}
)
.command(
"init-token-bridge",
"Initialize token bridge contract",
(yargs) => {
return yargs
.option("network", NETWORK_OPTIONS)
.option("package-id", {
alias: "p",
describe: "Token bridge package ID",
required: true,
type: "string",
})
.option("wormhole-state", {
alias: "w",
describe: "Wormhole state object ID",
required: true,
type: "string",
})
.option("governance-chain-id", {
alias: "c",
describe: "Governance chain ID",
default: GOVERNANCE_CHAIN,
type: "number",
required: false,
})
.option("governance-address", {
alias: "a",
describe: "Governance contract address",
type: "string",
default: GOVERNANCE_EMITTER,
required: false,
})
.option("private-key", PRIVATE_KEY_OPTIONS)
.option("rpc", RPC_OPTIONS);
},
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const packageId = argv["package-id"];
const wormholeStateObjectId = argv["wormhole-state"];
const governanceChainId = argv["governance-chain-id"];
const governanceContract = argv["governance-address"];
const privateKey = argv["private-key"];
const rpc = argv.rpc ?? NETWORKS[network].sui.rpc;
const res = await initTokenBridge(
network,
packageId,
wormholeStateObjectId,
governanceChainId,
governanceContract,
rpc,
privateKey
);
logTransactionDigest(res);
logTransactionSender(res);
console.log(
"Token bridge state object ID",
getCreatedObjects(res).find((e) =>
isSameType(e.type, `${packageId}::state::State`)
).objectId
);
}
)
.command(
"init-wormhole",
"Initialize wormhole core contract",
(yargs) => {
return yargs
.option("network", NETWORK_OPTIONS)
.option("package-id", {
alias: "p",
describe: "Core bridge package ID",
required: true,
type: "string",
})
.option("initial-guardian", {
alias: "i",
required: true,
describe: "Initial guardian public keys",
type: "string",
})
.option("debug", DEBUG_OPTIONS)
.option("governance-chain-id", {
alias: "c",
describe: "Governance chain ID",
default: GOVERNANCE_CHAIN,
type: "number",
required: false,
})
.option("guardian-set-index", {
alias: "s",
describe: "Governance set index",
default: 0,
type: "number",
required: false,
})
.option("governance-address", {
alias: "a",
describe: "Governance contract address",
type: "string",
default: GOVERNANCE_EMITTER,
required: false,
})
.option("private-key", PRIVATE_KEY_OPTIONS)
.option("rpc", RPC_OPTIONS);
},
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const packageId = argv["package-id"];
const initialGuardian = argv["initial-guardian"];
const debug = argv.debug ?? false;
const governanceChainId = argv["governance-chain-id"];
const guardianSetIndex = argv["guardian-set-index"];
const governanceContract = argv["governance-address"];
const privateKey = argv["private-key"];
const rpc = argv.rpc;
const res = await initWormhole(
network,
packageId,
initialGuardian,
governanceChainId,
guardianSetIndex,
governanceContract,
rpc,
privateKey
);
logTransactionDigest(res);
console.log(
"Wormhole state object ID",
getCreatedObjects(res).find((e) =>
isSameType(e.type, `${packageId}::state::State`)
).objectId
);
if (debug) {
logTransactionSender(res);
}
}
);
export const initExampleApp = async (
network: Network,
packageId: string,
wormholeStateObjectId: string,
rpc?: string,
privateKey?: string
): Promise<SuiTransactionBlockResponse> => {
rpc = rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
const signer = getSigner(provider, network, privateKey);
const transactionBlock = new TransactionBlock();
if (network === "DEVNET") {
// Avoid Error checking transaction input objects: GasBudgetTooHigh { gas_budget: 50000000000, max_budget: 10000000000 }
transactionBlock.setGasBudget(10000000000);
}
transactionBlock.moveCall({
target: `${packageId}::sender::init_with_params`,
arguments: [transactionBlock.object(wormholeStateObjectId)],
});
return executeTransactionBlock(signer, transactionBlock);
};
export const initTokenBridge = async (
network: Network,
tokenBridgePackageId: string,
coreBridgeStateObjectId: string,
governanceChainId: number,
governanceContract: string,
rpc?: string,
privateKey?: string
): Promise<SuiTransactionBlockResponse> => {
rpc = rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
const signer = getSigner(provider, network, privateKey);
const owner = await signer.getAddress();
const deployerCapObjectId = await getOwnedObjectId(
provider,
owner,
tokenBridgePackageId,
"setup",
"DeployerCap"
);
if (!deployerCapObjectId) {
throw new Error(
`Token bridge cannot be initialized because deployer capability cannot be found under ${owner}. Is the package published?`
);
}
const upgradeCapObjectId = await getUpgradeCapObjectId(
provider,
owner,
tokenBridgePackageId
);
if (!upgradeCapObjectId) {
throw new Error(
`Token bridge cannot be initialized because upgrade capability cannot be found under ${owner}. Is the package published?`
);
}
const wormholePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const transactionBlock = new TransactionBlock();
if (network === "DEVNET") {
// Avoid Error checking transaction input objects: GasBudgetTooHigh { gas_budget: 50000000000, max_budget: 10000000000 }
transactionBlock.setGasBudget(10000000000);
}
const [emitterCap] = transactionBlock.moveCall({
target: `${wormholePackageId}::emitter::new`,
arguments: [transactionBlock.object(coreBridgeStateObjectId)],
});
transactionBlock.moveCall({
target: `${tokenBridgePackageId}::setup::complete`,
arguments: [
transactionBlock.object(deployerCapObjectId),
transactionBlock.object(upgradeCapObjectId),
emitterCap,
transactionBlock.pure(governanceChainId),
transactionBlock.pure([...Buffer.from(governanceContract, "hex")]),
],
});
return executeTransactionBlock(signer, transactionBlock);
};
export const initWormhole = async (
network: Network,
coreBridgePackageId: string,
initialGuardians: string,
governanceChainId: number,
guardianSetIndex: number,
governanceContract: string,
rpc?: string,
privateKey?: string
): Promise<SuiTransactionBlockResponse> => {
rpc = rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
const signer = getSigner(provider, network, privateKey);
const owner = await signer.getAddress();
const deployerCapObjectId = await getOwnedObjectId(
provider,
owner,
coreBridgePackageId,
"setup",
"DeployerCap"
);
if (!deployerCapObjectId) {
throw new Error(
`Wormhole cannot be initialized because deployer capability cannot be found under ${owner}. Is the package published?`
);
}
const upgradeCapObjectId = await getUpgradeCapObjectId(
provider,
owner,
coreBridgePackageId
);
if (!upgradeCapObjectId) {
throw new Error(
`Wormhole cannot be initialized because upgrade capability cannot be found under ${owner}. Is the package published?`
);
}
const transactionBlock = new TransactionBlock();
if (network === "DEVNET") {
// Avoid Error checking transaction input objects: GasBudgetTooHigh { gas_budget: 50000000000, max_budget: 10000000000 }
transactionBlock.setGasBudget(10000000000);
}
transactionBlock.moveCall({
target: `${coreBridgePackageId}::setup::complete`,
arguments: [
transactionBlock.object(deployerCapObjectId),
transactionBlock.object(upgradeCapObjectId),
transactionBlock.pure(governanceChainId),
transactionBlock.pure([...Buffer.from(governanceContract, "hex")]),
transactionBlock.pure(guardianSetIndex),
transactionBlock.pure(
initialGuardians.split(",").map((g) => [...Buffer.from(g, "hex")])
),
transactionBlock.pure(24 * 60 * 60), // Guardian set TTL in seconds
transactionBlock.pure("0"), // Message fee
],
});
return executeTransactionBlock(signer, transactionBlock);
};

View File

@ -0,0 +1,116 @@
import {
normalizeSuiAddress,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import yargs from "yargs";
import { NETWORK_OPTIONS, RPC_OPTIONS } from "../../consts";
import { NETWORKS } from "../../networks";
import {
executeTransactionBlock,
getProvider,
getSigner,
logTransactionDigest,
logTransactionSender,
} from "../../sui";
import { assertNetwork } from "../../utils";
import { YargsAddCommandsFn } from "../Yargs";
export const addPublishMessageCommands: YargsAddCommandsFn = (
y: typeof yargs
) =>
y.command(
"publish-example-message",
"Publish message from example app via core bridge",
(yargs) => {
return yargs
.option("network", NETWORK_OPTIONS)
.option("package-id", {
alias: "p",
describe: "Package ID/module address",
required: true,
type: "string",
})
.option("state", {
alias: "s",
describe: "Core messages app state object ID",
required: true,
type: "string",
})
.option("wormhole-state", {
alias: "w",
describe: "Wormhole state object ID",
required: true,
type: "string",
})
.option("message", {
alias: "m",
describe: "Message payload",
required: true,
type: "string",
})
.option("private-key", {
alias: "k",
describe: "Custom private key to sign txs",
required: false,
type: "string",
})
.option("rpc", RPC_OPTIONS);
},
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const packageId = argv["package-id"];
const stateObjectId = argv["state"];
const wormholeStateObjectId = argv["wormhole-state"];
const message = argv["message"];
const privateKey = argv["private-key"];
const rpc = argv.rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
const signer = getSigner(provider, network, privateKey);
// Publish message
const transactionBlock = new TransactionBlock();
if (network === "DEVNET") {
// Avoid Error checking transaction input objects: GasBudgetTooHigh { gas_budget: 50000000000, max_budget: 10000000000 }
transactionBlock.setGasBudget(10000000000);
}
transactionBlock.moveCall({
target: `${packageId}::sender::send_message_entry`,
arguments: [
transactionBlock.object(stateObjectId),
transactionBlock.object(wormholeStateObjectId),
transactionBlock.pure(message),
transactionBlock.object(SUI_CLOCK_OBJECT_ID),
],
});
const res = await executeTransactionBlock(signer, transactionBlock);
// Hacky way to grab event since we don't require package ID of the
// core bridge as input. Doesn't really matter since this is a test
// command.
const event = res.events.find(
(e) =>
normalizeSuiAddress(e.packageId) === normalizeSuiAddress(packageId) &&
e.type.includes("publish_message::WormholeMessage")
);
if (!event) {
throw new Error(
"Couldn't find publish event. Events: " +
JSON.stringify(res.events, null, 2)
);
}
logTransactionDigest(res);
logTransactionSender(res);
console.log("Publish message succeeded:", {
sender: event.sender,
type: event.type,
payload: Buffer.from(event.parsedJson.payload).toString(),
emitter: Buffer.from(event.parsedJson.sender).toString("hex"),
sequence: event.parsedJson.sequence,
nonce: event.parsedJson.nonce,
});
}
);

View File

@ -0,0 +1,240 @@
import {
ChainId,
coalesceChainName,
parseTokenBridgeRegisterChainVaa,
} from "@certusone/wormhole-sdk";
import {
JsonRpcProvider,
TransactionBlock,
getObjectFields,
getTransactionDigest,
} from "@mysten/sui.js";
import dotenv from "dotenv";
import fs from "fs";
import yargs from "yargs";
import {
GOVERNANCE_CHAIN,
GOVERNANCE_EMITTER,
INITIAL_GUARDIAN_DEVNET,
RPC_OPTIONS,
} from "../../consts";
import { NETWORKS } from "../../networks";
import {
assertSuccess,
executeTransactionBlock,
getCreatedObjects,
getProvider,
getPublishedPackageId,
getSigner,
isSameType,
logPublishedPackageId,
logTransactionDigest,
registerChain,
} from "../../sui";
import { YargsAddCommandsFn } from "../Yargs";
import { deploy } from "./deploy";
import { initExampleApp, initTokenBridge, initWormhole } from "./init";
export const addSetupCommands: YargsAddCommandsFn = (y: typeof yargs) =>
y.command(
"setup-devnet",
"Setup devnet by deploying and initializing core and token bridges and submitting chain registrations.",
(yargs) => {
return yargs
.option("private-key", {
alias: "k",
describe: "Custom private key to sign txs",
required: false,
type: "string",
})
.option("rpc", RPC_OPTIONS);
},
async (argv) => {
const network = "DEVNET";
const privateKey = argv["private-key"];
const rpc = argv.rpc ?? NETWORKS[network].sui.rpc;
// Deploy core bridge
console.log("[1/4] Deploying core bridge...");
const coreBridgeDeployRes = await deploy(
network,
"wormhole",
rpc,
privateKey
);
assertSuccess(coreBridgeDeployRes, "Core bridge deployment failed.");
logTransactionDigest(coreBridgeDeployRes);
logPublishedPackageId(coreBridgeDeployRes);
// Init core bridge
console.log("\n[2/4] Initializing core bridge...");
const coreBridgePackageId = getPublishedPackageId(coreBridgeDeployRes);
const coreBridgeInitRes = await initWormhole(
network,
coreBridgePackageId,
INITIAL_GUARDIAN_DEVNET,
GOVERNANCE_CHAIN,
0,
GOVERNANCE_EMITTER,
rpc,
privateKey
);
const coreBridgeStateObjectId = getCreatedObjects(coreBridgeInitRes).find(
(e) => isSameType(e.type, `${coreBridgePackageId}::state::State`)
).objectId;
assertSuccess(coreBridgeInitRes, "Core bridge initialization failed.");
logTransactionDigest(coreBridgeInitRes);
console.log("Core bridge state object ID", coreBridgeStateObjectId);
// Deploy token bridge
console.log("\n[3/4] Deploying token bridge...");
const tokenBridgeDeployRes = await deploy(
network,
"token_bridge",
rpc,
privateKey
);
assertSuccess(tokenBridgeDeployRes, "Token bridge deployment failed.");
logTransactionDigest(tokenBridgeDeployRes);
logPublishedPackageId(tokenBridgeDeployRes);
// Init token bridge
console.log("\n[4/4] Initializing token bridge...");
const tokenBridgePackageId = getPublishedPackageId(tokenBridgeDeployRes);
const tokenBridgeInitRes = await initTokenBridge(
network,
tokenBridgePackageId,
coreBridgeStateObjectId,
GOVERNANCE_CHAIN,
GOVERNANCE_EMITTER,
rpc,
privateKey
);
const tokenBridgeStateObjectId = getCreatedObjects(
tokenBridgeInitRes
).find((e) =>
isSameType(e.type, `${tokenBridgePackageId}::state::State`)
).objectId;
assertSuccess(tokenBridgeInitRes, "Token bridge initialization failed.");
logTransactionDigest(tokenBridgeInitRes);
console.log("Token bridge state object ID", tokenBridgeStateObjectId);
// Deploy example app
console.log("\n[+1/3] Deploying example app...");
const exampleAppDeployRes = await deploy(
network,
"examples/core_messages",
rpc,
privateKey
);
logTransactionDigest(tokenBridgeDeployRes);
logPublishedPackageId(tokenBridgeDeployRes);
// Init example app
console.log("\n[+2/3] Initializing example app...");
const exampleAppPackageId = getPublishedPackageId(exampleAppDeployRes);
const exampleAppInitRes = await initExampleApp(
network,
exampleAppPackageId,
coreBridgeStateObjectId,
rpc,
privateKey
);
const exampleAppStateObjectId = getCreatedObjects(exampleAppInitRes).find(
(e) => isSameType(e.type, `${exampleAppPackageId}::sender::State`)
).objectId;
logTransactionDigest(exampleAppInitRes);
console.log("Example app state object ID", exampleAppStateObjectId);
// Deploy example coins
console.log("\n[+3/3] Deploying example coins...");
const coinsDeployRes = await deploy(
network,
"examples/coins",
rpc,
privateKey
);
logTransactionDigest(coinsDeployRes);
logPublishedPackageId(coinsDeployRes);
// Print publish message command for convenience
let publishCommand = `\nPublish message command: worm sui publish-example-message -n devnet -p "${exampleAppPackageId}" -s "${exampleAppStateObjectId}" -w "${coreBridgeStateObjectId}" -m "hello"`;
if (argv.rpc) publishCommand += ` -r "${argv.rpc}"`;
if (privateKey) publishCommand += ` -k "${privateKey}"`;
console.log(publishCommand);
// Dump summary
const provider = getProvider(network, rpc);
const emitterCapObjectId = await getEmitterCapObjectId(
provider,
tokenBridgeStateObjectId
);
console.log("\nSummary:");
console.log(" Core bridge package ID", coreBridgePackageId);
console.log(" Core bridge state object ID", coreBridgeStateObjectId);
console.log(" Token bridge package ID", tokenBridgePackageId);
console.log(" Token bridge state object ID", tokenBridgeStateObjectId);
console.log(" Token bridge emitter cap ID", emitterCapObjectId);
// Chain registrations
console.log("\nChain registrations:");
const envPath = `${process.cwd()}/.env`;
if (!fs.existsSync(envPath)) {
throw new Error(`Couldn't find .env file at ${envPath}.`);
}
dotenv.config({ path: envPath });
const signer = getSigner(provider, network, privateKey);
const tx = new TransactionBlock();
tx.setGasBudget(10000000000);
const registrations = [];
for (const key in process.env) {
if (/^REGISTER_(.+)_TOKEN_BRIDGE_VAA$/.test(key)) {
// Get VAA info
const vaa = Buffer.from(String(process.env[key]), "hex");
const { foreignChain, module } =
parseTokenBridgeRegisterChainVaa(vaa);
const chain = coalesceChainName(foreignChain as ChainId);
registrations.push({ chain, module });
// Register
await registerChain(
provider,
network,
vaa,
coreBridgeStateObjectId,
tokenBridgeStateObjectId,
tx
);
}
}
const registerRes = await executeTransactionBlock(signer, tx);
assertSuccess(registerRes, "Chain registrations failed.");
// Log registered bridges
for (const registration of registrations) {
console.log(` ${registration.chain} ${registration.module}... done`);
}
console.log("Transaction digest:", getTransactionDigest(registerRes));
// Done!
console.log("\nDone!");
}
);
const getEmitterCapObjectId = async (
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string
): Promise<string> => {
return getObjectFields(
await provider.getObject({
id: tokenBridgeStateObjectId,
options: {
showContent: true,
},
})
).emitter_cap.fields.id.id;
};

View File

@ -0,0 +1,106 @@
import yargs from "yargs";
import { NETWORK_OPTIONS, RPC_OPTIONS } from "../../consts";
import { NETWORKS } from "../../networks";
import { getPackageId, getProvider } from "../../sui";
import { assertNetwork } from "../../utils";
import { YargsAddCommandsFn } from "../Yargs";
export const addUtilsCommands: YargsAddCommandsFn = (y: typeof yargs) =>
y
.command(
"objects <owner>",
"Get owned objects by owner",
(yargs) =>
yargs
.positional("owner", {
describe: "Owner address",
type: "string",
})
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS),
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const rpc = argv.rpc ?? NETWORKS[network].sui.rpc;
const owner = argv.owner;
const provider = getProvider(network, rpc);
const objects = [];
let cursor = undefined;
while (true) {
const res = await provider.getOwnedObjects({ owner, cursor });
objects.push(...res.data);
if (res.hasNextPage) {
cursor = res.nextCursor;
} else {
break;
}
}
console.log("Network", network);
console.log("Owner", owner);
console.log("Objects", JSON.stringify(objects, null, 2));
}
)
.command(
"package-id <state-object-id>",
"Get package ID from State object ID",
(yargs) =>
yargs
.positional("state-object-id", {
describe: "Object ID of State object",
type: "string",
})
.option("network", NETWORK_OPTIONS)
.option("rpc", RPC_OPTIONS),
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const rpc = argv.rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
console.log(await getPackageId(provider, argv["state-object-id"]));
}
)
// This command is useful for debugging, especially when the Sui explorer
// goes down :)
.command(
"tx <transaction-digest>",
"Get transaction details",
(yargs) =>
yargs
.positional("transaction-digest", {
describe: "Digest of transaction to fetch",
type: "string",
})
.option("network", {
alias: "n",
describe: "Network",
type: "string",
choices: ["mainnet", "testnet", "devnet"],
default: "devnet",
required: false,
})
.option("rpc", RPC_OPTIONS),
async (argv) => {
const network = argv.network.toUpperCase();
assertNetwork(network);
const rpc = argv.rpc ?? NETWORKS[network].sui.rpc;
const provider = getProvider(network, rpc);
console.log(
JSON.stringify(
await provider.getTransactionBlock({
digest: argv["transaction-digest"],
options: {
showInput: true,
showEffects: true,
showEvents: true,
showObjectChanges: true,
},
}),
null,
2
)
);
}
);

View File

@ -1,20 +1,20 @@
import { config } from '../config';
import { spawnSync } from 'child_process';
import { spawnSync } from "child_process";
import { config } from "../config";
let dir = `${config.wormholeDir}/clients/js`;
exports.command = 'update';
exports.desc = 'Update this tool by rebuilding it';
exports.handler = function(_argv: any) {
if (isOutdated()) {
console.log(`Building in ${dir}...`);
spawnSync(`make build -C ${dir}`, { shell: true, stdio: 'inherit' });
} else {
console.log("'worm' is up to date");
}
}
exports.command = "update";
exports.desc = "Update this tool by rebuilding it";
exports.handler = function (_argv: any) {
if (isOutdated()) {
console.log(`Building in ${dir}...`);
spawnSync(`make build -C ${dir}`, { shell: true, stdio: "inherit" });
} else {
console.log("'worm' is up to date");
}
};
export function isOutdated(): boolean {
const result = spawnSync(`make build -C ${dir} --question`, { shell: true });
return result.status !== 0;
const result = spawnSync(`make build -C ${dir} --question`, { shell: true });
return result.status !== 0;
}

View File

@ -1,8 +1,14 @@
import { CONTRACTS as SDK_CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { ethers } from "ethers";
import {
CHAIN_ID_SOLANA,
CONTRACTS as SDK_CONTRACTS,
} from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
const OVERRIDES = {
MAINNET: {
sui: {
core: undefined,
token_bridge: undefined,
},
aptos: {
token_bridge:
"0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
@ -12,6 +18,11 @@ const OVERRIDES = {
},
},
TESTNET: {
sui: {
core: "0x69ae41bdef4770895eb4e7aaefee5e4673acc08f6917b4856cf55549c4573ca8",
token_bridge:
"0x32422cb2f929b6a4e3f81b4791ea11ac2af896b310f3d9442aa1fe924ce0bab4",
},
aptos: {
token_bridge:
"0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
@ -21,6 +32,11 @@ const OVERRIDES = {
},
},
DEVNET: {
sui: {
core: "0x04ca9f568b19c80b4fb429c26f7cc57b1ca97e7519ccd68af436dd2706808e01", // wormhole module State object ID
token_bridge:
"0x844b3ce3f9b2cd82cb8ad1a1962593f6a340c7bad0b4867b82a49463554883dd", // token_bridge module State object ID
},
aptos: {
token_bridge:
"0x84a5f374d29fc77e370014dce4fd6a55b58ad608de8074b0be5571701724da31",
@ -31,16 +47,50 @@ const OVERRIDES = {
},
};
// TODO(aki): move this to SDK at some point
export const CONTRACTS = {
MAINNET: { ...SDK_CONTRACTS.MAINNET, ...OVERRIDES.MAINNET },
TESTNET: { ...SDK_CONTRACTS.TESTNET, ...OVERRIDES.TESTNET },
DEVNET: { ...SDK_CONTRACTS.DEVNET, ...OVERRIDES.DEVNET },
};
export function evm_address(x: string): string {
return hex(x).substring(2).padStart(64, "0");
}
export const DEBUG_OPTIONS = {
alias: "d",
describe: "Log debug info",
type: "boolean",
required: false,
} as const;
export function hex(x: string): string {
return ethers.utils.hexlify(x, { allowMissingPrefix: true });
}
export const NAMED_ADDRESSES_OPTIONS = {
describe: "Named addresses in the format addr1=0x0,addr2=0x1,...",
type: "string",
require: false,
} as const;
export const NETWORK_OPTIONS = {
alias: "n",
describe: "Network",
type: "string",
choices: ["mainnet", "testnet", "devnet"],
required: true,
} as const;
export const PRIVATE_KEY_OPTIONS = {
alias: "k",
describe: "Custom private key to sign transactions",
required: false,
type: "string",
} as const;
export const RPC_OPTIONS = {
alias: "r",
describe: "Override default rpc endpoint url",
type: "string",
required: false,
} as const;
export const GOVERNANCE_CHAIN = CHAIN_ID_SOLANA;
export const GOVERNANCE_EMITTER =
"0000000000000000000000000000000000000000000000000000000000000004";
export const INITIAL_GUARDIAN_DEVNET =
"befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe";

View File

@ -1,16 +1,16 @@
import { CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { getNetworkInfo, Network } from "@injectivelabs/networks";
import { getStdFee, DEFAULT_STD_FEE } from "@injectivelabs/utils";
import {
PrivateKey,
TxGrpcApi,
ChainRestAuthApi,
createTransaction,
MsgExecuteContractCompat,
PrivateKey,
TxGrpcApi,
} from "@injectivelabs/sdk-ts";
import { DEFAULT_STD_FEE, getStdFee } from "@injectivelabs/utils";
import { fromUint8Array } from "js-base64";
import { impossible, Payload } from "./vaa";
import { NETWORKS } from "./networks";
import { CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { impossible, Payload } from "./vaa";
export async function execute_injective(
payload: Payload,
@ -56,7 +56,7 @@ export async function execute_injective(
console.log("Upgrading core contract");
break;
case "RecoverChainId":
throw new Error("RecoverChainId not supported on injective")
throw new Error("RecoverChainId not supported on injective");
default:
impossible(payload);
}
@ -80,7 +80,7 @@ export async function execute_injective(
console.log("Upgrading contract");
break;
case "RecoverChainId":
throw new Error("RecoverChainId not supported on injective")
throw new Error("RecoverChainId not supported on injective");
case "RegisterChain":
console.log("Registering chain");
break;
@ -108,7 +108,7 @@ export async function execute_injective(
console.log("Upgrading contract");
break;
case "RecoverChainId":
throw new Error("RecoverChainId not supported on injective")
throw new Error("RecoverChainId not supported on injective");
case "RegisterChain":
console.log("Registering chain");
break;

View File

@ -28,4 +28,7 @@ if (isOutdated()) {
);
}
yargs(hideBin(process.argv)).commandDir("cmds").strict().demandCommand().argv;
yargs(hideBin(process.argv))
.commandDir("cmds", { recurse: true })
.strict()
.demandCommand().argv;

View File

@ -111,14 +111,6 @@ const MAINNET = {
chain_id: "dimension_37-1",
key: get_env_var("XPLA_KEY"),
},
sei: {
rpc: undefined,
key: undefined,
},
sepolia: {
rpc: undefined,
key: undefined,
},
btc: {
rpc: undefined,
key: undefined,
@ -156,6 +148,14 @@ const MAINNET = {
rpc: undefined,
key: get_env_var("ETH_KEY"),
},
sei: {
rpc: undefined,
key: undefined,
},
sepolia: {
rpc: undefined,
key: undefined,
},
};
const TESTNET = {
@ -241,8 +241,8 @@ const TESTNET = {
key: get_env_var("APTOS_TESTNET"),
},
sui: {
rpc: undefined,
key: undefined,
rpc: "https://fullnode.devnet.sui.io:443",
key: get_env_var("SUI_KEY_TESTNET"),
},
pythnet: {
rpc: "https://api.pythtest.pyth.network/",
@ -396,7 +396,7 @@ const DEVNET = {
sepolia: {
rpc: undefined,
key: undefined,
},
},
wormchain: {
rpc: "http://localhost:1319",
chain_id: "wormchain",
@ -407,8 +407,8 @@ const DEVNET = {
key: "537c1f91e56891445b491068f519b705f8c0f1a1e66111816dd5d4aa85b8113d",
},
sui: {
rpc: undefined,
key: undefined,
rpc: "http://0.0.0.0:9000",
key: "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb",
},
moonbeam: {
rpc: undefined,

View File

@ -9,11 +9,12 @@
"version": "0.0.3",
"dependencies": {
"@celo-tools/celo-ethers-wrapper": "^0.1.0",
"@certusone/wormhole-sdk": "^0.9.14",
"@certusone/wormhole-sdk": "^0.9.15-beta.4",
"@cosmjs/encoding": "^0.26.2",
"@injectivelabs/networks": "^1.10.7",
"@injectivelabs/sdk-ts": "^1.10.47",
"@injectivelabs/utils": "^1.10.5",
"@mysten/sui.js": "^0.32.2",
"@sei-js/core": "^1.3.2",
"@solana/web3.js": "^1.22.0",
"@terra-money/terra.js": "^3.1.3",
@ -22,6 +23,7 @@
"algosdk": "^1.15.0",
"aptos": "^1.3.16",
"axios": "^0.24.0",
"base-64": "^1.0.0",
"binary-parser": "^2.0.2",
"bn.js": "^5.2.0",
"bs58": "^4.0.1",
@ -489,9 +491,9 @@
}
},
"node_modules/@certusone/wormhole-sdk": {
"version": "0.9.14",
"resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.14.tgz",
"integrity": "sha512-xR1dkMkzWJz+EfIvlzThQ5AkU6oY1UjRsyxaxvDEcd9NxZMRHfXJSgHFdP8gWjDfg3nUnj4NGY/UeqAxq9l1+g==",
"version": "0.9.15-beta.4",
"resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.15-beta.4.tgz",
"integrity": "sha512-HZwCK1wGrXjcP7w9qkbXtNNU1p3qgY+Q6RF5Y+6gIV85bOHJSCWkqbvkFTINf/NW6lPuTuKplYmoEaN8F1mFQA==",
"dependencies": {
"@certusone/wormhole-sdk-proto-web": "0.0.6",
"@certusone/wormhole-sdk-wasm": "^0.0.1",
@ -499,6 +501,7 @@
"@injectivelabs/networks": "1.10.7",
"@injectivelabs/sdk-ts": "1.10.47",
"@injectivelabs/utils": "1.10.5",
"@mysten/sui.js": "0.32.2",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.3.5",
"@solana/web3.js": "^1.66.2",
@ -2200,6 +2203,160 @@
"integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==",
"dev": true
},
"node_modules/@mysten/bcs": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-0.7.1.tgz",
"integrity": "sha512-wFPb8bkhwrbiStfZMV5rFM7J+umpke59/dNjDp+UYJKykNlW23LCk2ePyEUvGdb62HGJM1jyOJ8g4egE3OmdKA==",
"dependencies": {
"bs58": "^5.0.0"
}
},
"node_modules/@mysten/bcs/node_modules/base-x": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
},
"node_modules/@mysten/bcs/node_modules/bs58": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
"dependencies": {
"base-x": "^4.0.0"
}
},
"node_modules/@mysten/sui.js": {
"version": "0.32.2",
"resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
"integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
"dependencies": {
"@mysten/bcs": "0.7.1",
"@noble/curves": "^1.0.0",
"@noble/hashes": "^1.3.0",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@suchipi/femver": "^1.0.0",
"jayson": "^4.0.0",
"rpc-websockets": "^7.5.1",
"superstruct": "^1.0.3",
"tweetnacl": "^1.0.3"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@mysten/sui.js/node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@mysten/sui.js/node_modules/@scure/bip32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
"integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/curves": "~1.0.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@mysten/sui.js/node_modules/@scure/bip39": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@mysten/sui.js/node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"node_modules/@mysten/sui.js/node_modules/jayson": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
"integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
"dependencies": {
"@types/connect": "^3.4.33",
"@types/node": "^12.12.54",
"@types/ws": "^7.4.4",
"commander": "^2.20.3",
"delay": "^5.0.0",
"es6-promisify": "^5.0.0",
"eyes": "^0.1.8",
"isomorphic-ws": "^4.0.1",
"json-stringify-safe": "^5.0.1",
"JSONStream": "^1.3.5",
"uuid": "^8.3.2",
"ws": "^7.4.5"
},
"bin": {
"jayson": "bin/jayson.js"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@mysten/sui.js/node_modules/superstruct": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@mysten/sui.js/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@noble/curves": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
"integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "1.3.0"
}
},
"node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@noble/ed25519": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz",
@ -2811,6 +2968,11 @@
"node": ">=12.20.0"
}
},
"node_modules/@suchipi/femver": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz",
"integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg=="
},
"node_modules/@terra-money/legacy.proto": {
"name": "@terra-money/terra.proto",
"version": "0.1.7",
@ -3425,6 +3587,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base-64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
},
"node_modules/base-x": {
"version": "3.0.8",
"license": "MIT",
@ -6670,9 +6837,9 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/rpc-websockets": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz",
"integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==",
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.1.tgz",
"integrity": "sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"eventemitter3": "^4.0.7",
@ -8065,9 +8232,9 @@
"requires": {}
},
"@certusone/wormhole-sdk": {
"version": "0.9.14",
"resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.14.tgz",
"integrity": "sha512-xR1dkMkzWJz+EfIvlzThQ5AkU6oY1UjRsyxaxvDEcd9NxZMRHfXJSgHFdP8gWjDfg3nUnj4NGY/UeqAxq9l1+g==",
"version": "0.9.15-beta.4",
"resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.15-beta.4.tgz",
"integrity": "sha512-HZwCK1wGrXjcP7w9qkbXtNNU1p3qgY+Q6RF5Y+6gIV85bOHJSCWkqbvkFTINf/NW6lPuTuKplYmoEaN8F1mFQA==",
"requires": {
"@certusone/wormhole-sdk-proto-web": "0.0.6",
"@certusone/wormhole-sdk-wasm": "^0.0.1",
@ -8075,6 +8242,7 @@
"@injectivelabs/networks": "1.10.7",
"@injectivelabs/sdk-ts": "1.10.47",
"@injectivelabs/utils": "1.10.5",
"@mysten/sui.js": "0.32.2",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.3.5",
"@solana/web3.js": "^1.66.2",
@ -9433,6 +9601,121 @@
"integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==",
"dev": true
},
"@mysten/bcs": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-0.7.1.tgz",
"integrity": "sha512-wFPb8bkhwrbiStfZMV5rFM7J+umpke59/dNjDp+UYJKykNlW23LCk2ePyEUvGdb62HGJM1jyOJ8g4egE3OmdKA==",
"requires": {
"bs58": "^5.0.0"
},
"dependencies": {
"base-x": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
},
"bs58": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
"requires": {
"base-x": "^4.0.0"
}
}
}
},
"@mysten/sui.js": {
"version": "0.32.2",
"resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
"integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
"requires": {
"@mysten/bcs": "0.7.1",
"@noble/curves": "^1.0.0",
"@noble/hashes": "^1.3.0",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@suchipi/femver": "^1.0.0",
"jayson": "^4.0.0",
"rpc-websockets": "^7.5.1",
"superstruct": "^1.0.3",
"tweetnacl": "^1.0.3"
},
"dependencies": {
"@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
},
"@scure/bip32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
"integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
"requires": {
"@noble/curves": "~1.0.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"@scure/bip39": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"requires": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"jayson": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
"integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
"requires": {
"@types/connect": "^3.4.33",
"@types/node": "^12.12.54",
"@types/ws": "^7.4.4",
"commander": "^2.20.3",
"delay": "^5.0.0",
"es6-promisify": "^5.0.0",
"eyes": "^0.1.8",
"isomorphic-ws": "^4.0.1",
"json-stringify-safe": "^5.0.1",
"JSONStream": "^1.3.5",
"uuid": "^8.3.2",
"ws": "^7.4.5"
}
},
"superstruct": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
"@noble/curves": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
"integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
"requires": {
"@noble/hashes": "1.3.0"
},
"dependencies": {
"@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
}
}
},
"@noble/ed25519": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz",
@ -9964,6 +10247,11 @@
"superstruct": "^0.14.2"
}
},
"@suchipi/femver": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz",
"integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg=="
},
"@terra-money/legacy.proto": {
"version": "npm:@terra-money/terra.proto@0.1.7",
"resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz",
@ -10496,6 +10784,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base-64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
},
"base-x": {
"version": "3.0.8",
"requires": {
@ -13214,9 +13507,9 @@
}
},
"rpc-websockets": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz",
"integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==",
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.1.tgz",
"integrity": "sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==",
"requires": {
"@babel/runtime": "^7.17.2",
"bufferutil": "^4.0.1",

View File

@ -3,11 +3,12 @@
"version": "0.0.3",
"dependencies": {
"@celo-tools/celo-ethers-wrapper": "^0.1.0",
"@certusone/wormhole-sdk": "^0.9.14",
"@certusone/wormhole-sdk": "^0.9.15-beta.4",
"@cosmjs/encoding": "^0.26.2",
"@injectivelabs/networks": "^1.10.7",
"@injectivelabs/sdk-ts": "^1.10.47",
"@injectivelabs/utils": "^1.10.5",
"@mysten/sui.js": "^0.32.2",
"@sei-js/core": "^1.3.2",
"@solana/web3.js": "^1.22.0",
"@terra-money/terra.js": "^3.1.3",
@ -16,6 +17,7 @@
"algosdk": "^1.15.0",
"aptos": "^1.3.16",
"axios": "^0.24.0",
"base-64": "^1.0.0",
"binary-parser": "^2.0.2",
"bn.js": "^5.2.0",
"bs58": "^4.0.1",

153
clients/js/sui/MoveToml.ts Normal file
View File

@ -0,0 +1,153 @@
import fs from "fs";
import { ParsedMoveToml } from "./types";
export class MoveToml {
private toml: ParsedMoveToml;
constructor(tomlPathOrStr: string) {
let tomlStr = tomlPathOrStr;
try {
tomlStr = fs.readFileSync(tomlPathOrStr, "utf8").toString();
} catch (e) {}
this.toml = MoveToml.parse(tomlStr);
}
addRow(sectionName: string, key: string, value: string) {
if (!MoveToml.isValidValue(value)) {
if (/^\S+$/.test(value)) {
value = `"${value}"`;
} else {
throw new Error(`Invalid value "${value}"`);
}
}
const section = this.forceGetSection(sectionName);
section.rows.push({ key, value });
return this;
}
addOrUpdateRow(sectionName: string, key: string, value: string) {
if (this.getRow(sectionName, key) === undefined) {
this.addRow(sectionName, key, value);
} else {
this.updateRow(sectionName, key, value);
}
return this;
}
getSectionNames(): string[] {
return this.toml.map((s) => s.name);
}
isPublished(): boolean {
return !!this.getRow("package", "published-at");
}
removeRow(sectionName: string, key: string) {
const section = this.forceGetSection(sectionName);
section.rows = section.rows.filter((r) => r.key !== key);
return this;
}
serialize(): string {
let tomlStr = "";
for (let i = 0; i < this.toml.length; i++) {
const section = this.toml[i];
tomlStr += `[${section.name}]\n`;
for (const row of section.rows) {
tomlStr += `${row.key} = ${row.value}\n`;
}
if (i !== this.toml.length - 1) {
tomlStr += "\n";
}
}
return tomlStr;
}
updateRow(sectionName: string, key: string, value: string) {
if (!MoveToml.isValidValue(value)) {
if (/^\S+$/.test(value)) {
value = `"${value}"`;
} else {
throw new Error(`Invalid value "${value}"`);
}
}
const row = this.forceGetRow(sectionName, key);
row.value = value;
return this;
}
static isValidValue(value: string): boolean {
value = value.trim();
return (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("{") && value.endsWith("}")) ||
(value.startsWith("'") && value.endsWith("'"))
);
}
static parse(tomlStr: string): ParsedMoveToml {
const toml: ParsedMoveToml = [];
const lines = tomlStr.split("\n");
for (const line of lines) {
// Parse new section
const sectionMatch = line.trim().match(/^\[(\S+)\]$/);
if (sectionMatch && sectionMatch.length === 2) {
toml.push({ name: sectionMatch[1], rows: [] });
continue;
}
// Otherwise, parse row in section. We must handle two cases:
// 1. value is string, e.g. name = "MyPackage"
// 2. value is object, e.g. Sui = { local = "../sui-framework" }
const rowMatch = line.trim().match(/^([a-zA-Z_\-]+) = (.+)$/);
if (rowMatch && rowMatch.length === 3) {
toml[toml.length - 1].rows.push({
key: rowMatch[1],
value: rowMatch[2],
});
}
}
return toml;
}
private forceGetRow(
sectionName: string,
key: string
): ParsedMoveToml[number]["rows"][number] {
const section = this.forceGetSection(sectionName);
const row = section.rows.find((r) => r.key === key);
if (row === undefined) {
throw new Error(`Row "${key}" not found in section "${sectionName}"`);
}
return row;
}
private forceGetSection(sectionName: string): ParsedMoveToml[number] {
const section = this.getSection(sectionName);
if (section === undefined) {
console.log(this.toml);
throw new Error(`Section "${sectionName}" not found`);
}
return section;
}
private getRow(
sectionName: string,
key: string
): ParsedMoveToml[number]["rows"][number] | undefined {
const section = this.getSection(sectionName);
return section && section.rows.find((r) => r.key === key);
}
private getSection(sectionName: string): ParsedMoveToml[number] | undefined {
return this.toml.find((s) => s.name === sectionName);
}
}

129
clients/js/sui/buildCoin.ts Normal file
View File

@ -0,0 +1,129 @@
import { JsonRpcProvider } from "@mysten/sui.js";
import fs from "fs";
import { Network } from "../utils";
import { MoveToml } from "./MoveToml";
import {
buildPackage,
cleanupTempToml,
getAllLocalPackageDependencyPaths,
getDefaultTomlPath,
getPackageNameFromPath,
setupMainToml,
} from "./publish";
import { SuiBuildOutput } from "./types";
import { getPackageId } from "./utils";
export const buildCoin = async (
provider: JsonRpcProvider,
network: Network,
packagePath: string,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
version: string,
decimals: number
): Promise<SuiBuildOutput> => {
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
try {
setupCoin(
network,
packagePath,
coreBridgePackageId,
tokenBridgePackageId,
version,
decimals
);
return buildPackage(`${packagePath}/wrapped_coin`);
} finally {
cleanupCoin(`${packagePath}/wrapped_coin`);
}
};
const setupCoin = (
network: Network,
packagePath: string,
coreBridgePackageId: string,
tokenBridgePackageId: string,
version: string,
decimals: number
): void => {
// Check to see if the given version string is valid. We don't include the
// end boundary in the regex to accomodate versions such as V__0_1_0_patch,
// in the off chance we need such a naming scheme.
if (!/^V__[0-9]+_[0-9]+_[0-9]+/.test(version)) {
throw new Error(`Invalid version ${version}`);
}
// Assemble package directory
fs.rmSync(`${packagePath}/wrapped_coin`, { recursive: true, force: true });
fs.mkdirSync(`${packagePath}/wrapped_coin/sources`, { recursive: true });
// Replace template variables
const coinTemplate = fs
.readFileSync(
`${packagePath}/templates/wrapped_coin/sources/coin.move`,
"utf8"
)
.toString();
const coin = coinTemplate
.replace(/{{DECIMALS}}/, decimals.toString())
.replace(/{{VERSION}}/g, version);
fs.writeFileSync(
`${packagePath}/wrapped_coin/sources/coin.move`,
coin,
"utf8"
);
// Substitute dependency package IDs
const toml = new MoveToml(`${packagePath}/templates/wrapped_coin/Move.toml`)
.updateRow("addresses", "wormhole", coreBridgePackageId)
.updateRow("addresses", "token_bridge", tokenBridgePackageId)
.serialize();
const tomlPath = `${packagePath}/wrapped_coin/Move.toml`;
fs.writeFileSync(tomlPath, toml, "utf8");
// Setup dependencies
const paths = getAllLocalPackageDependencyPaths(tomlPath);
for (const dependencyPath of paths) {
// todo(aki): the 4th param is a hack that makes this work, but doesn't
// necessarily make sense. We should probably revisit this later.
setupMainToml(dependencyPath, network, false, network !== "DEVNET");
if (network === "DEVNET") {
const dependencyToml = new MoveToml(getDefaultTomlPath(dependencyPath));
switch (getPackageNameFromPath(dependencyPath)) {
case "wormhole":
dependencyToml
.addOrUpdateRow("package", "published-at", coreBridgePackageId)
.updateRow("addresses", "wormhole", coreBridgePackageId);
break;
case "token_bridge":
dependencyToml
.addOrUpdateRow("package", "published-at", tokenBridgePackageId)
.updateRow("addresses", "token_bridge", tokenBridgePackageId);
break;
default:
throw new Error(`Unknown dependency ${dependencyPath}`);
}
fs.writeFileSync(
getDefaultTomlPath(dependencyPath),
dependencyToml.serialize(),
"utf8"
);
}
}
};
const cleanupCoin = (packagePath: string) => {
const paths = getAllLocalPackageDependencyPaths(
getDefaultTomlPath(packagePath)
);
for (const dependencyPath of paths) {
cleanupTempToml(dependencyPath, false);
}
};

7
clients/js/sui/error.ts Normal file
View File

@ -0,0 +1,7 @@
export class SuiRpcValidationError extends Error {
constructor(response: any) {
super(
`Sui RPC returned an unexpected response: ${JSON.stringify(response)}`
);
}
}

7
clients/js/sui/index.ts Normal file
View File

@ -0,0 +1,7 @@
export * from "./MoveToml";
export * from "./buildCoin";
export * from "./log";
export * from "./publish";
export * from "./submit";
export * from "./types";
export * from "./utils";

28
clients/js/sui/log.ts Normal file
View File

@ -0,0 +1,28 @@
import {
getTransactionDigest,
getTransactionSender,
SuiTransactionBlockResponse,
} from "@mysten/sui.js";
import { getCreatedObjects, getPublishedPackageId } from "./utils";
export const logTransactionDigest = (
res: SuiTransactionBlockResponse,
...args: string[]
) => {
console.log("Transaction digest", getTransactionDigest(res), ...args);
};
export const logTransactionSender = (res: SuiTransactionBlockResponse) => {
console.log("Transaction sender", getTransactionSender(res));
};
export const logPublishedPackageId = (res: SuiTransactionBlockResponse) => {
console.log("Published to", getPublishedPackageId(res));
};
export const logCreatedObjects = (res: SuiTransactionBlockResponse) => {
console.log(
"Created objects",
JSON.stringify(getCreatedObjects(res), null, 2)
);
};

248
clients/js/sui/publish.ts Normal file
View File

@ -0,0 +1,248 @@
import {
fromB64,
getPublishedObjectChanges,
normalizeSuiObjectId,
RawSigner,
TransactionBlock,
} from "@mysten/sui.js";
import { execSync } from "child_process";
import fs from "fs";
import { resolve } from "path";
import { Network } from "../utils";
import { MoveToml } from "./MoveToml";
import { SuiBuildOutput } from "./types";
import { executeTransactionBlock } from "./utils";
export const buildPackage = (packagePath: string): SuiBuildOutput => {
if (!fs.existsSync(packagePath)) {
throw new Error(`Package not found at ${packagePath}`);
}
return JSON.parse(
execSync(
`sui move build --dump-bytecode-as-base64 --path ${packagePath} 2> /dev/null`,
{
encoding: "utf-8",
}
)
);
};
/**
* Get Move.toml dependencies by looking for all lines of form 'local = ".*"'.
* This works because network-specific Move.toml files should not contain
* dev addresses, so the only lines that match this regex are the dependencies
* that need to be replaced.
* @param packagePath
* @returns
*/
export const getAllLocalPackageDependencyPaths = (
tomlPath: string
): string[] => {
const tomlStr = fs.readFileSync(tomlPath, "utf8").toString();
const toml = new MoveToml(tomlStr);
// Sanity check that Move.toml does not contain dev info since this breaks
// building and publishing packages
if (
toml.getSectionNames().some((name) => name.includes("dev-dependencies")) ||
toml.getSectionNames().some((name) => name.includes("dev-addresses"))
) {
throw new Error(
"Network-specific Move.toml should not contain dev-dependencies or dev-addresses."
);
}
const packagePath = getPackagePathFromTomlPath(tomlPath);
return [...tomlStr.matchAll(/local = "(.*)"/g)].map((match) =>
resolve(packagePath, match[1])
);
};
export const getDefaultTomlPath = (packagePath: string): string =>
`${packagePath}/Move.toml`;
export const getPackageNameFromPath = (packagePath: string): string =>
packagePath.split("/").pop() || "";
export const publishPackage = async (
signer: RawSigner,
network: Network,
packagePath: string
) => {
try {
setupMainToml(packagePath, network);
const build = buildPackage(packagePath);
// Publish contracts
const tx = new TransactionBlock();
if (network === "DEVNET") {
// Avoid Error checking transaction input objects: GasBudgetTooHigh { gas_budget: 50000000000, max_budget: 10000000000 }
tx.setGasBudget(10000000000);
}
const [upgradeCap] = tx.publish({
modules: build.modules.map((m) => Array.from(fromB64(m))),
dependencies: build.dependencies.map((d) => normalizeSuiObjectId(d)),
});
// Transfer upgrade capability to deployer
tx.transferObjects([upgradeCap], tx.pure(await signer.getAddress()));
// Execute transactions
const res = await executeTransactionBlock(signer, tx);
// Update network-specific Move.toml with package ID
const publishEvents = getPublishedObjectChanges(res);
if (publishEvents.length !== 1) {
throw new Error(
"No publish event found in transaction:" +
JSON.stringify(res.objectChanges, null, 2)
);
}
updateNetworkToml(packagePath, network, publishEvents[0].packageId);
// Return publish transaction info
return res;
} finally {
cleanupTempToml(packagePath);
}
};
export const cleanupTempToml = (
packagePath: string,
cleanupDependencies: boolean = true
): void => {
const defaultTomlPath = getDefaultTomlPath(packagePath);
const tempTomlPath = getTempTomlPath(packagePath);
if (fs.existsSync(tempTomlPath)) {
// Clean up Move.toml for dependencies
if (cleanupDependencies) {
const dependencyPaths =
getAllLocalPackageDependencyPaths(defaultTomlPath);
for (const path of dependencyPaths) {
cleanupTempToml(path);
}
}
fs.renameSync(tempTomlPath, defaultTomlPath);
}
};
const getPackagePathFromTomlPath = (tomlPath: string): string =>
tomlPath.split("/").slice(0, -1).join("/");
const getTempTomlPath = (packagePath: string): string =>
`${packagePath}/Move.temp.toml`;
const getTomlPathByNetwork = (packagePath: string, network: Network): string =>
`${packagePath}/Move.${network.toLowerCase()}.toml`;
const resetNetworkToml = (
packagePath: string,
network: Network,
recursive: boolean = false
): void => {
const networkTomlPath = getTomlPathByNetwork(packagePath, network);
const tomlStr = fs.readFileSync(networkTomlPath, "utf8").toString();
const toml = new MoveToml(tomlStr);
if (toml.isPublished()) {
if (recursive) {
const dependencyPaths =
getAllLocalPackageDependencyPaths(networkTomlPath);
for (const path of dependencyPaths) {
resetNetworkToml(path, network);
}
}
const updatedTomlStr = toml
.removeRow("package", "published-at")
.updateRow("addresses", getPackageNameFromPath(packagePath), "_")
.serialize();
fs.writeFileSync(networkTomlPath, updatedTomlStr, "utf8");
}
};
export const setupMainToml = (
packagePath: string,
network: Network,
checkDependencies: boolean = true,
isDependency: boolean = false
): void => {
const defaultTomlPath = getDefaultTomlPath(packagePath);
const tempTomlPath = getTempTomlPath(packagePath);
const srcTomlPath = getTomlPathByNetwork(packagePath, network);
if (fs.existsSync(tempTomlPath)) {
// It's possible that this dependency has been set up by another package
if (isDependency) {
return;
}
throw new Error("Move.temp.toml exists, is there a publish in progress?");
}
// Make deploying on devnet more convenient by resetting Move.toml so we
// don't have to manually reset them repeatedly during local development.
// This is not recursive because we assume that packages are deployed bottom
// up.
if (!isDependency && network === "DEVNET") {
resetNetworkToml(packagePath, network);
}
// Save default Move.toml
if (!fs.existsSync(defaultTomlPath)) {
throw new Error(
`Invalid package layout. Move.toml not found at ${defaultTomlPath}`
);
}
fs.renameSync(defaultTomlPath, tempTomlPath);
// Set Move.toml from appropriate network
if (!fs.existsSync(srcTomlPath)) {
throw new Error(`Move.toml for ${network} not found at ${srcTomlPath}`);
}
fs.copyFileSync(srcTomlPath, defaultTomlPath);
// Replace undefined addresses in base Move.toml
const tomlStr = fs.readFileSync(defaultTomlPath, "utf8").toString();
const toml = new MoveToml(tomlStr);
const packageName = getPackageNameFromPath(packagePath);
if (!isDependency) {
if (toml.isPublished()) {
throw new Error(`Package ${packageName} is already published.`);
} else {
toml.updateRow("addresses", packageName, "0x0");
}
fs.writeFileSync(defaultTomlPath, toml.serialize());
} else if (isDependency && !toml.isPublished()) {
throw new Error(
`Dependency ${packageName} is not published. Please publish it first.`
);
}
// Set up Move.toml for dependencies
if (checkDependencies) {
const dependencyPaths = getAllLocalPackageDependencyPaths(defaultTomlPath);
for (const path of dependencyPaths) {
setupMainToml(path, network, checkDependencies, true);
}
}
};
const updateNetworkToml = (
packagePath: string,
network: Network,
packageId: string
): void => {
const tomlPath = getTomlPathByNetwork(packagePath, network);
const tomlStr = fs.readFileSync(tomlPath, "utf8");
const updatedTomlStr = new MoveToml(tomlStr)
.addRow("package", "published-at", packageId)
.updateRow("addresses", getPackageNameFromPath(packagePath), packageId)
.serialize();
fs.writeFileSync(tomlPath, updatedTomlStr, "utf8");
};

211
clients/js/sui/submit.ts Normal file
View File

@ -0,0 +1,211 @@
import {
assertChain,
createWrappedOnSui,
createWrappedOnSuiPrepare,
getForeignAssetSui,
parseAttestMetaVaa,
} from "@certusone/wormhole-sdk";
import { getWrappedCoinType } from "@certusone/wormhole-sdk/lib/cjs/sui";
import {
CHAIN_ID_SUI,
CHAIN_ID_TO_NAME,
CONTRACTS,
} from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import { SUI_CLOCK_OBJECT_ID, TransactionBlock } from "@mysten/sui.js";
import { Network } from "../utils";
import { Payload, impossible } from "../vaa";
import {
assertSuccess,
executeTransactionBlock,
getPackageId,
getProvider,
getSigner,
isSuiCreateEvent,
isSuiPublishEvent,
registerChain,
} from "./utils";
export const submit = async (
payload: Payload,
vaa: Buffer,
network: Network,
rpc?: string,
privateKey?: string
) => {
const consoleWarnTemp = console.warn;
console.warn = () => {};
const chain = CHAIN_ID_TO_NAME[CHAIN_ID_SUI];
const provider = getProvider(network, rpc);
const signer = getSigner(provider, network, privateKey);
switch (payload.module) {
case "Core": {
const coreObjectId = CONTRACTS[network][chain].core;
if (!coreObjectId) {
throw Error("Core bridge object ID is undefined");
}
const corePackageId = await getPackageId(provider, coreObjectId);
switch (payload.type) {
case "ContractUpgrade":
throw new Error("ContractUpgrade not supported on Sui");
case "GuardianSetUpgrade": {
console.log("Submitting new guardian set");
const tx = new TransactionBlock();
setMaxGasBudgetDevnet(network, tx);
tx.moveCall({
target: `${corePackageId}::wormhole::update_guardian_set`,
arguments: [
tx.object(coreObjectId),
tx.pure([...vaa]),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
const result = await executeTransactionBlock(signer, tx);
console.log(JSON.stringify(result));
break;
}
case "RecoverChainId":
throw new Error("RecoverChainId not supported on Sui");
default:
impossible(payload);
}
break;
}
case "NFTBridge": {
throw new Error("NFT bridge not supported on Sui");
}
case "TokenBridge": {
const coreBridgeStateObjectId = CONTRACTS[network][chain].core;
if (!coreBridgeStateObjectId) {
throw Error("Core bridge object ID is undefined");
}
const tokenBridgeStateObjectId = CONTRACTS[network][chain].token_bridge;
if (!tokenBridgeStateObjectId) {
throw Error("Token bridge object ID is undefined");
}
switch (payload.type) {
case "AttestMeta": {
// Test attest VAA: 01000000000100d87023087588d8a482d6082c57f3c93649c9a61a98848fc3a0b271f4041394ff7b28abefc8e5e19b83f45243d073d677e122e41425c2dbae3eb5ae1c7c0ac0ee01000000c056a8000000020000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16000000000000000001020000000000000000000000002d8be6bf0baa74e0a907016679cae9190e80dd0a000212544b4e0000000000000000000000000000000000000000000000000000000000457468657265756d205465737420546f6b656e00000000000000000000000000
const { tokenChain, tokenAddress } = parseAttestMetaVaa(vaa);
assertChain(tokenChain);
const coinType = await getForeignAssetSui(
provider,
tokenBridgeStateObjectId,
tokenChain,
tokenAddress
);
if (coinType) {
// Coin already exists, so we update it
console.log("Updating wrapped asset...");
throw new Error("Updating wrapped asset not supported on Sui");
} else {
// Coin doesn't exist, so create wrapped asset
console.log("[1/2] Creating wrapped asset...");
const prepareTx = await createWrappedOnSuiPrepare(
provider,
coreBridgeStateObjectId,
tokenBridgeStateObjectId,
parseAttestMetaVaa(vaa).decimals,
await signer.getAddress()
);
setMaxGasBudgetDevnet(network, prepareTx);
const prepareRes = await executeTransactionBlock(signer, prepareTx);
assertSuccess(prepareRes, "Prepare registration failed.");
const coinPackageId =
prepareRes.objectChanges.find(isSuiPublishEvent).packageId;
console.log(` Digest ${prepareRes.digest}`);
console.log(` Published to ${coinPackageId}`);
console.log(` Type ${getWrappedCoinType(coinPackageId)}`);
if (!rpc && network !== "DEVNET") {
// Wait for wrapped asset creation to be propogated to other
// nodes in case this complete registration call is load balanced
// to another node.
await sleep(5000);
}
console.log("\n[2/2] Registering asset...");
const wrappedAssetSetup = prepareRes.objectChanges
.filter(isSuiCreateEvent)
.find((e) =>
/create_wrapped::WrappedAssetSetup/.test(e.objectType)
);
const completeTx = await createWrappedOnSui(
provider,
coreBridgeStateObjectId,
tokenBridgeStateObjectId,
await signer.getAddress(),
coinPackageId,
wrappedAssetSetup.objectType,
vaa
);
setMaxGasBudgetDevnet(network, completeTx);
const completeRes = await executeTransactionBlock(
signer,
completeTx
);
assertSuccess(completeRes, "Complete registration failed.");
console.log(` Digest ${completeRes.digest}`);
console.log("\nDone!");
}
break;
}
case "ContractUpgrade":
throw new Error("ContractUpgrade not supported on Sui");
case "RecoverChainId":
throw new Error("RecoverChainId not supported on Sui");
case "RegisterChain": {
console.log("Registering chain");
const tx = await registerChain(
provider,
network,
vaa,
coreBridgeStateObjectId,
tokenBridgeStateObjectId
);
setMaxGasBudgetDevnet(network, tx);
const res = await executeTransactionBlock(signer, tx);
console.log(JSON.stringify(res));
break;
}
case "Transfer":
throw new Error("Transfer not supported on Sui");
case "TransferWithPayload":
throw Error("Can't complete payload 3 transfer from CLI");
default:
impossible(payload);
break;
}
break;
}
default:
impossible(payload);
}
console.warn = consoleWarnTemp;
};
/**
* Currently, (Sui SDK version 0.32.2 and Sui 1.0.0 testnet), there is a
* mismatch in the max gas budget that causes an error when executing a
* transaction. Because these values are hardcoded, we set the max gas budget
* as a temporary workaround.
* @param network
* @param tx
*/
const setMaxGasBudgetDevnet = (network: Network, tx: TransactionBlock) => {
if (network === "DEVNET") {
// Avoid Error checking transaction input objects: GasBudgetTooHigh { gas_budget: 50000000000, max_budget: 10000000000 }
tx.setGasBudget(10000000000);
}
};
const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

9
clients/js/sui/types.ts Normal file
View File

@ -0,0 +1,9 @@
export type ParsedMoveToml = {
name: string;
rows: { key: string; value: string }[];
}[];
export type SuiBuildOutput = {
modules: string[];
dependencies: string[];
};

377
clients/js/sui/utils.ts Normal file
View File

@ -0,0 +1,377 @@
import {
Connection,
Ed25519Keypair,
JsonRpcProvider,
PaginatedObjectsResponse,
RawSigner,
SUI_CLOCK_OBJECT_ID,
SuiTransactionBlockResponse,
TransactionBlock,
fromB64,
getPublishedObjectChanges,
normalizeSuiAddress,
} from "@mysten/sui.js";
import { NETWORKS } from "../networks";
import { Network } from "../utils";
import { Payload, VAA, parse, serialiseVAA } from "../vaa";
import { SuiRpcValidationError } from "./error";
const UPGRADE_CAP_TYPE = "0x2::package::UpgradeCap";
export const assertSuccess = (
res: SuiTransactionBlockResponse,
error: string
): void => {
if (res?.effects?.status?.status !== "success") {
throw new Error(`${error} Response: ${JSON.stringify(res)}`);
}
};
export const executeTransactionBlock = async (
signer: RawSigner,
transactionBlock: TransactionBlock
): Promise<SuiTransactionBlockResponse> => {
// As of version 0.32.2, Sui SDK outputs a RPC validation warning when the
// SDK falls behind the Sui version used by the RPC. We silence these
// warnings since the SDK is often out of sync with the RPC.
const consoleWarnTemp = console.warn;
console.warn = () => {};
// Let caller handle parsing and logging info
const res = await signer.signAndExecuteTransactionBlock({
transactionBlock,
options: {
showInput: true,
showEffects: true,
showEvents: true,
showObjectChanges: true,
},
});
console.warn = consoleWarnTemp;
return res;
};
export const findOwnedObjectByType = async (
provider: JsonRpcProvider,
owner: string,
type: string,
cursor?: string
): Promise<string | null> => {
const res: PaginatedObjectsResponse = await provider.getOwnedObjects({
owner,
filter: undefined, // Filter must be undefined to avoid 504 responses
cursor: cursor || undefined,
options: {
showType: true,
},
});
if (!res || !res.data) {
throw new SuiRpcValidationError(res);
}
const object = res.data.find((d) => d.data.type === type);
if (!object && res.hasNextPage) {
return findOwnedObjectByType(
provider,
owner,
type,
res.nextCursor as string
);
} else if (!object && !res.hasNextPage) {
return null;
} else {
return object.data.objectId;
}
};
export const getCreatedObjects = (
res: SuiTransactionBlockResponse
): { type: string; objectId: string; owner: string }[] => {
return res.objectChanges.filter(isSuiCreateEvent).map((e) => ({
type: e.objectType,
objectId: e.objectId,
owner: e.owner["AddressOwner"] || e.owner["ObjectOwner"] || e.owner,
}));
};
export const getOwnedObjectId = async (
provider: JsonRpcProvider,
owner: string,
packageId: string,
moduleName: string,
structName: string
): Promise<string | null> => {
const type = `${packageId}::${moduleName}::${structName}`;
// Upgrade caps are a special case
if (normalizeSuiType(type) === normalizeSuiType(UPGRADE_CAP_TYPE)) {
throw new Error(
"`getOwnedObjectId` should not be used to get the object ID of an `UpgradeCap`. Use `getUpgradeCapObjectId` instead."
);
}
try {
const res = await provider.getOwnedObjects({
owner,
filter: { StructType: type },
options: {
showContent: true,
},
});
if (!res || !res.data) {
throw new SuiRpcValidationError(res);
}
const objects = res.data.filter((o) => o.data?.objectId);
if (objects.length === 1) {
return objects[0].data?.objectId;
} else if (objects.length > 1) {
const objectsStr = JSON.stringify(objects, null, 2);
throw new Error(
`Found multiple objects owned by ${owner} of type ${type}. This may mean that we've received an unexpected response from the Sui RPC and \`worm\` logic needs to be updated to handle this. Objects: ${objectsStr}`
);
} else {
return null;
}
} catch (error) {
// Handle 504 error by using findOwnedObjectByType method
const is504HttpError = `${error}`.includes("504 Gateway Time-out");
if (error && is504HttpError) {
return findOwnedObjectByType(provider, owner, type);
} else {
throw error;
}
}
};
// TODO(kp): remove this once it's in the sdk
export async function getPackageId(
provider: JsonRpcProvider,
stateObjectId: string
): Promise<string> {
const fields = await provider
.getObject({
id: stateObjectId,
options: {
showContent: true,
},
})
.then((result) => {
if (result.data?.content?.dataType === "moveObject") {
return result.data.content.fields;
}
throw new Error("Not a moveObject");
});
if ("upgrade_cap" in fields) {
return fields.upgrade_cap.fields.package;
}
throw new Error("upgrade_cap not found");
}
export const getProvider = (
network?: Network,
rpc?: string
): JsonRpcProvider => {
if (!network && !rpc) {
throw new Error("Must provide network or RPC to initialize provider");
}
rpc = rpc || NETWORKS[network]["sui"].rpc;
if (!rpc) {
throw new Error(`No default RPC found for Sui ${network}`);
}
return new JsonRpcProvider(new Connection({ fullnode: rpc }));
};
export const getPublishedPackageId = (
res: SuiTransactionBlockResponse
): string => {
const publishEvents = getPublishedObjectChanges(res);
if (publishEvents.length !== 1) {
throw new Error(
"Unexpected number of publish events found:" +
JSON.stringify(publishEvents, null, 2)
);
}
return publishEvents[0].packageId;
};
export const getSigner = (
provider: JsonRpcProvider,
network: Network,
customPrivateKey?: string
): RawSigner => {
const privateKey: string | undefined =
customPrivateKey || NETWORKS[network]["sui"].key;
if (!privateKey) {
throw new Error(`No private key found for Sui ${network}`);
}
const bytes = fromB64(privateKey);
const keypair = Ed25519Keypair.fromSecretKey(bytes.slice(1));
return new RawSigner(keypair, provider);
};
/**
* This function returns the object ID of the `UpgradeCap` that belongs to the
* given package and owner if it exists.
*
* Structs created by the Sui framework such as `UpgradeCap`s all have the same
* type (e.g. `0x2::package::UpgradeCap`) and have a special field, `package`,
* we can use to differentiate them.
* @param provider Sui RPC provider
* @param owner Address of the current owner of the `UpgradeCap`
* @param packageId ID of the package that the `UpgradeCap` was created for
* @returns The object ID of the `UpgradeCap` if it exists, otherwise `null`
*/
export const getUpgradeCapObjectId = async (
provider: JsonRpcProvider,
owner: string,
packageId: string
): Promise<string | null> => {
const res = await provider.getOwnedObjects({
owner,
filter: { StructType: UPGRADE_CAP_TYPE },
options: {
showContent: true,
},
});
if (!res || !res.data) {
throw new SuiRpcValidationError(res);
}
const objects = res.data.filter(
(o) =>
o.data?.objectId &&
o.data?.content?.dataType === "moveObject" &&
o.data?.content?.fields?.package === packageId
);
if (objects.length === 1) {
// We've found the object we're looking for
return objects[0].data?.objectId;
} else if (objects.length > 1) {
const objectsStr = JSON.stringify(objects, null, 2);
throw new Error(
`Found multiple upgrade capabilities owned by ${owner} from package ${packageId}. Objects: ${objectsStr}`
);
} else {
return null;
}
};
export const isSameType = (a: string, b: string) => {
try {
return normalizeSuiType(a) === normalizeSuiType(b);
} catch (e) {
return false;
}
};
export const isSuiCreateEvent = <
T extends SuiTransactionBlockResponse["objectChanges"][number],
K extends Extract<T, { type: "created" }>
>(
event: T
): event is K => {
return event.type === "created";
};
export const isSuiPublishEvent = <
T extends SuiTransactionBlockResponse["objectChanges"][number],
K extends Extract<T, { type: "published" }>
>(
event: T
): event is K => {
return event.type === "published";
};
export const isValidSuiAddress = (objectId: string): boolean => {
return /^(0x)?[0-9a-f]{1,64}$/.test(objectId);
};
// todo(aki): this needs to correctly handle types such as
// 0x2::dynamic_field::Field<0x3c6d386861470e6f9cb35f3c91f69e6c1f1737bd5d217ca06a15f582e1dc1ce3::state::MigrationControl, bool>
export const normalizeSuiType = (type: string): string => {
const tokens = type.split("::");
if (tokens.length !== 3 || !isValidSuiAddress(tokens[0])) {
throw new Error(`Invalid Sui type: ${type}`);
}
return [normalizeSuiAddress(tokens[0]), tokens[1], tokens[2]].join("::");
};
export const registerChain = async (
provider: JsonRpcProvider,
network: Network,
vaa: Buffer,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
transactionBlock?: TransactionBlock
): Promise<TransactionBlock> => {
if (network === "DEVNET") {
// Modify the VAA to only have 1 guardian signature
// TODO: remove this when we can deploy the devnet core contract
// deterministically with multiple guardians in the initial guardian set
// Currently the core contract is setup with only 1 guardian in the set
const parsedVaa = parse(vaa);
parsedVaa.signatures = [parsedVaa.signatures[0]];
vaa = Buffer.from(serialiseVAA(parsedVaa as VAA<Payload>), "hex");
}
// Get package IDs
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
// Register chain
let tx = transactionBlock;
if (!tx) {
tx = new TransactionBlock();
tx.setGasBudget(1000000);
}
// Get VAA
const [verifiedVaa] = tx.moveCall({
target: `${coreBridgePackageId}::vaa::parse_and_verify`,
arguments: [
tx.object(coreBridgeStateObjectId),
tx.pure([...vaa]),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
// Get decree ticket
const [decreeTicket] = tx.moveCall({
target: `${tokenBridgePackageId}::register_chain::authorize_governance`,
arguments: [tx.object(tokenBridgeStateObjectId)],
});
// Get decree receipt
const [decreeReceipt] = tx.moveCall({
target: `${coreBridgePackageId}::governance_message::verify_vaa`,
arguments: [tx.object(coreBridgeStateObjectId), verifiedVaa, decreeTicket],
typeArguments: [
`${tokenBridgePackageId}::register_chain::GovernanceWitness`,
],
});
// Register chain
tx.moveCall({
target: `${tokenBridgePackageId}::register_chain::register_chain`,
arguments: [tx.object(tokenBridgeStateObjectId), decreeReceipt],
});
return tx;
};

34
clients/js/utils.ts Normal file
View File

@ -0,0 +1,34 @@
import { spawnSync } from "child_process";
import { ethers } from "ethers";
import { config } from "./config";
export type Network = "MAINNET" | "TESTNET" | "DEVNET";
export function assertNetwork(n: string): asserts n is Network {
if (n !== "MAINNET" && n !== "TESTNET" && n !== "DEVNET") {
throw Error(`Unknown network: ${n}`);
}
}
export const checkBinary = (binaryName: string, dirName?: string): void => {
const binary = spawnSync(binaryName, ["--version"]);
if (binary.status !== 0) {
console.error(
`${binaryName} is not installed. Please install ${binaryName} and try again.`
);
if (dirName) {
console.error(
`See ${config.wormholeDir}/${dirName}/README.md for instructions.`
);
}
process.exit(1);
}
};
export const evm_address = (x: string): string => {
return hex(x).substring(2).padStart(64, "0");
};
export const hex = (x: string): string => {
return ethers.utils.hexlify(x, { allowMissingPrefix: true });
};

View File

@ -41,6 +41,7 @@ spec:
- --deterministic
- --time="1970-01-01T00:00:00+00:00"
- --host=0.0.0.0
- --accounts=11
ports:
- containerPort: 8545
name: rpc

View File

@ -42,6 +42,7 @@ spec:
- --deterministic
- --time="1970-01-01T00:00:00+00:00"
- --host=0.0.0.0
- --accounts=11
- --chainId=1397
ports:
- containerPort: 8545

View File

@ -7,11 +7,8 @@ metadata:
spec:
ports:
- name: node
port: 9002
port: 9000
targetPort: node
- name: ws
port: 9001
targetPort: ws
- name: prometheus
port: 9184
targetPort: prometheus
@ -41,17 +38,17 @@ spec:
containers:
- name: sui-node
image: sui-node
resources:
requests:
memory: "2048Mi"
command:
- /bin/sh
- -c
- /tmp/start_node.sh
- /bin/sh
- -c
- /tmp/scripts/start_node.sh
ports:
- containerPort: 9002
- containerPort: 9000
name: node
protocol: TCP
- containerPort: 9001
name: ws
protocol: TCP
- containerPort: 9184
name: prometheus
protocol: TCP
@ -60,6 +57,18 @@ spec:
protocol: TCP
readinessProbe:
tcpSocket:
port: 9002
restartPolicy: Always
port: 9000
- name: sui-contracts
image: sui-node
command: ["/bin/bash", "-c"]
args:
[
"cd /tmp && ./scripts/wait_for_devnet.sh && worm sui setup-devnet && touch success && sleep infinity",
]
readinessProbe:
periodSeconds: 5
failureThreshold: 300
exec:
command:
- cat
- /tmp/success

View File

@ -6,7 +6,7 @@
| Test ERC20 | ETH | 0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A | Tokens minted to Test Wallet |
| Test NFT | ETH | 0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66 | One minted to Test Wallet |
| Test WETH | ETH | 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E | Tokens minted to Test Wallet |
| Test ERC20 GA | ETH | 0xf19A2A01B70519f67ADb309a994Ec8c69A967E8b | Tokens minted to Test Wallet 9 |
| Test ERC20 GA | ETH | 0x4cFB3F70BF6a80397C2e634e5bDd85BC0bb189EE | Tokens minted to Test Wallet 9 |
| Bridge Core | ETH | 0xC89Ce4735882C9F0f0FE26686c53074E09B0D550 | |
| Token Bridge | ETH | 0x0290FB167208Af455bB137780163b7B7a9a10C16 | |
| NFT Bridge | ETH | 0x26b4afb60d6c903165150c6f0aa14f8016be4aec | |

View File

@ -84,7 +84,7 @@ module.exports = async function(callback) {
console.log("WETH token deployed at: " + wethAddress);
for (let idx = 2; idx < 10; idx++) {
for (let idx = 2; idx < 11; idx++) {
await token.methods.mint(accounts[idx], "1000000000000000000000").send({
from: accounts[0],
gas: 1000000,

View File

@ -1,464 +1,415 @@
{
"global": {
"governanceChainId": "1",
"governanceEmitterAddress": "0000000000000000000000000000000000000000000000000000000000000004"
},
"urls": {
"guardianSetLocalUrl": "http://localhost:7071/v1/guardianset/current"
},
"chains": {
"1": {
"rpcUrlTilt": "http://solana-devnet:8899",
"rpcUrlLocal": "http://localhost:8899",
"rpcPort": "8899",
"contracts": {
"coreEmitterAddress": "VVStPLdubtbBUnMDhC3kt8fM3AE6NLRv73TAd3FCAen",
"coreNativeAddress": "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o",
"tokenBridgeEmitterAddress": "ENG1wQ7CQKH8ibAJ1hSLmJgL9Ucg6DRDbj752ZAfidLA",
"tokenBridgeNativeAddress": "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE",
"nftBridgeEmitterAddress": "BABAnMBgBELTQnHabZqYa1thHKp834RDvud4rJ8EUr3k",
"nftBridgeNativeAddress": "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA"
},
"accounts": {
"testWallet": {
"public": "6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J",
"private": [
14,
173,
153,
4,
176,
224,
201,
111,
32,
237,
183,
185,
159,
247,
22,
161,
89,
84,
215,
209,
212,
137,
10,
92,
157,
49,
29,
192,
101,
164,
152,
70,
87,
65,
8,
174,
214,
157,
175,
126,
98,
90,
54,
24,
100,
177,
247,
77,
19,
112,
47,
44,
165,
109,
233,
102,
14,
86,
109,
29,
134,
145,
132,
141
]
}
},
"addresses": {
"testToken": {
"address": "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ",
"name": "Solana Test Token",
"symbol": "SOLT",
"decimals": 6
},
"testNFT": {
"address": "BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna",
"name": "Not a PUNK🎸",
"symbol": "PUNK🎸",
"decimals": 0
},
"testNFT2": {
"address": "nftMANh29jbMboVnbYt1AUAWFP9N4Jnckr9Zeq85WUs",
"name": "Not a PUNK2🎸",
"symbol": "PUNK2🎸",
"decimals": 0
}
}
},
"2": {
"rpcUrlTilt": "http://eth-devnet:8545",
"rpcUrlLocal": "http://localhost:8545",
"rpcPort": "8545",
"contracts": {
"coreEmitterAddress": "000000000000000000000000c89ce4735882c9f0f0fe26686c53074e09b0d550",
"coreNativeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
"tokenBridgeEmitterAddress": "0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16",
"tokenBridgeNativeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
"nftBridgeEmitterAddress": "00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec",
"nftBridgeAddress": "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
},
"accounts": {
"testWallet": {
"public": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"private": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
"mnemonic": "myth like bonus scare over problem client lizard pioneer submit female collect"
}
},
"addresses": {
"testToken": {
"address": "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A",
"name": "Ethereum Test Token",
"symbol": "TKN",
"decimals": 18
},
"testNFT": {
"address": "0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66",
"name": "Not an APE 🐒",
"symbol": "APE🐒",
"decimals": 0
},
"testWETH": {
"address": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E",
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18
},
"testGA": {
"address": "0xf19A2A01B70519f67ADb309a994Ec8c69A967E8b",
"name": "Accountant Test Token",
"symbol": "GA",
"decimals": 18
}
}
},
"3": {
"rpcUrlTilt": "http://terra-terrad:1317",
"rpcUrlLocal": "http://localhost:1317",
"rpcPort": "1317",
"contracts": {
"coreEmitterAddress": "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
"coreNativeAddress": "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
"tokenBridgeEmitterAddress": "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4",
"tokenBridgeNativeAddress": "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4",
"nftBridgeEmitterAddress": "terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl",
"nftBridgeNativeAddress": "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"
},
"accounts": {
"testWallet": {
"public": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v",
"private": "",
"mnemonic": "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
}
},
"addresses": {
"testToken": {
"address": "terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh",
"name": "MOCK",
"symbol": "MCK",
"decimals": 6
},
"testNFT": {
"address": "terra18dt935pdcn2ka6l0syy5gt20wa48n3mktvdvjj",
"name": "MOCK",
"symbol": "MCK",
"decimals": 0
}
}
},
"4": {
"rpcUrlTilt": "http://eth-devnet2:8546",
"rpcUrlLocal": "http://localhost:8546",
"rpcPort": "8546",
"contracts": {
"coreEmitterAddress": "000000000000000000000000c89ce4735882c9f0f0fe26686c53074e09b0d550",
"coreNativeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
"tokenBridgeEmitterAddress": "0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16",
"tokenBridgeNativeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
"nftBridgeEmitterAddress": "00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec",
"nftBridgeAddress": "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
}
},
"8": {
"contracts": {
"tokenBridgeEmitterAddress": "8edf5b0e108c3a1a0a4b704cc89591f2ad8d50df24e991567e640ed720a94be2"
}
},
"15": {
"contracts": {
"tokenBridgeEmitterAddress": "e83c99874cb2d60921648a438606f5ffcf60c2e26ef13678b2e57fab3def6a30",
"nftBridgeEmitterAddress": "11aaad1c851095bf97ee1be9ed1161a47187aba8b71bf6821efac391d8ee95f7"
}
},
"18": {
"rpcUrlTilt": "http://terra2-terrad:1317",
"rpcUrlLocal": "http://localhost:1318",
"rpcPort": "1318",
"contracts": {
"coreEmitterAddress": "terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au",
"coreNativeAddress": "terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au",
"tokenBridgeEmitterAddress": "terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6",
"tokenBridgeNativeAddress": "terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6"
},
"accounts": {
"testWallet": {
"public": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v",
"private": "",
"mnemonic": "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
}
},
"addresses": {
"testToken": {
"address": "terra1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqynf7kp",
"name": "MOCK",
"symbol": "MCK",
"decimals": 6
}
}
},
"3104": {
"rpcUrlTilt": "http://wormchain:1317",
"rpcUrlLocal": "http://localhost:1319",
"tendermintUrlTilt": "http://wormchain:26657",
"tendermintUrlLocal": "http://localhost:26659",
"rpcPort": "1317",
"tendermintPort": "26657",
"contracts": {
"coreEmitterAddress": "wormhole1ap5vgur5zlgys8whugfegnn43emka567dtq0jl",
"coreNativeAddress": "wormhole1ap5vgur5zlgys8whugfegnn43emka567dtq0jl",
"tokenBridgeEmitterAddress": "wormhole1zugu6cajc4z7ue29g9wnes9a5ep9cs7yu7rn3z",
"tokenBridgeNativeAddress": "wormhole1zugu6cajc4z7ue29g9wnes9a5ep9cs7yu7rn3z",
"accountingNativeAddress": "wormhole14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9srrg465"
},
"accounts": {
"wormchainNodeOfGuardian0": {
"address": "C10820983F33456CE7BEB3A046F5A83FA34F027D",
"addressBase64": "wQggmD8zRWznvrOgRvWoP6NPAn0=",
"addressWormhole": "000000000000000000000000c10820983f33456ce7beb3a046f5a83fa34f027d",
"public": "wormhole1cyyzpxplxdzkeea7kwsydadg87357qna3zg3tq",
"privateHex": "48d23cc417a30674e907a2403f109f082d92e197823d02e6a423c6aeb8e41204",
"cosmos.crypto.secp256k1.PubKey": "AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG",
"tendermint/PrivKeyEd25519": "DONGe0wxovG1ZuCQ1iMbyBCW/hG5UeKz6ZFfhdZYznRSC48Lc1nwhUwXzHtXfwAOY0mO3mhTy4CMwPeYFvBZ1A==",
"mnemonic": "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
},
"wormchainValidator0": {
"public": "wormholevaloper1cyyzpxplxdzkeea7kwsydadg87357qna87hzv8",
"tendermint/PubKeyEd25519": "fnfoo/C+i+Ng1J8vct6wfvrTS9JeNIG5UeO87ZHKMkY=",
"tendermint/PrivKeyEd25519": "Zb3gQZSd8qNMyXUQdKmeqM/SSYeVDD80S4XPEsCAgPN+d+ij8L6L42DUny9y3rB++tNL0l40gblR47ztkcoyRg=="
},
"wormchainNodeOfGuardian1": {
"address": "701C475B19A3F68D3FDEBF09591487FACEF2D636",
"addressWormhole": "000000000000000000000000701c475b19a3f68d3fdebf09591487facef2d636",
"addressBase64": "cBxHWxmj9o0/3r8JWRSH+s7y1jY=",
"public": "wormhole1wqwywkce50mg6077huy4j9y8lt80943ks5udzr",
"privateHex": "7095b73fa951fd117d54f3bca130b8088625db2d60d94d4f064791dc1a792b29",
"cosmos.crypto.secp256k1.PubKey": "ApJi/CY2RGyzA5cQtDwU9c+o7T8OE+SjrgcG5PwLMjTP",
"tendermint/PrivKeyEd25519": "TTdzb3XLJbSXP/5VhzPJCWysCDDH2hEXTqdvLI6RYk7rxPwzCXTprp2ZEfSCfQswYgUUQgO9JKzbAtfyeK2G1A==",
"mnemonic": "maple pudding enjoy pole real rabbit soft make square city wrestle area aisle dwarf spike voice over still post lend genius bitter exit shoot"
},
"wormchainValidator1": {
"public": "wormholevaloper1wqwywkce50mg6077huy4j9y8lt80943kxgr79y",
"tendermint/PubKeyEd25519": "Zcujkt1sXRWWLfhgxLAm/Q+ioLn4wFim0OnGPLlCG0I=",
"tendermint/PrivKeyEd25519": "SGWIYI3BgC/dxNOk1gYx6LpChAKqWGtAfZSx0SDFWuhly6OS3WxdFZYt+GDEsCb9D6KgufjAWKbQ6cY8uUIbQg=="
}
},
"addresses": {
"native": {
"address": "uworm",
"addressWormhole": "010c0ded78f1b69ec7b79b9ee592fbbcacebc97db1c695220a833135bfa74824",
"denom": "uworm",
"name": "worm",
"symbol": "worm",
"decimals": 0
},
"testToken": {
"address": "wormhole1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqhnev3f",
"addressWormhole": "003f822e9066cfea09b9ce1247e8f79a86a24dda2d8b3d76a608ae7583220411",
"name": "MOCK",
"symbol": "MCK",
"decimals": 6
}
}
},
"22": {
"contracts": {
"tokenBridgeEmitterAddress": "0000000000000000000000000000000000000000000000000000000000000001",
"nftBridgeEmitterAddress": "0000000000000000000000000000000000000000000000000000000000000002"
}
"global": {
"governanceChainId": "1",
"governanceEmitterAddress": "0000000000000000000000000000000000000000000000000000000000000004"
},
"urls": {
"guardianSetLocalUrl": "http://localhost:7071/v1/guardianset/current"
},
"chains": {
"1": {
"rpcUrlTilt": "http://solana-devnet:8899",
"rpcUrlLocal": "http://localhost:8899",
"rpcPort": "8899",
"contracts": {
"coreEmitterAddress": "VVStPLdubtbBUnMDhC3kt8fM3AE6NLRv73TAd3FCAen",
"coreNativeAddress": "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o",
"tokenBridgeEmitterAddress": "ENG1wQ7CQKH8ibAJ1hSLmJgL9Ucg6DRDbj752ZAfidLA",
"tokenBridgeNativeAddress": "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE",
"nftBridgeEmitterAddress": "BABAnMBgBELTQnHabZqYa1thHKp834RDvud4rJ8EUr3k",
"nftBridgeNativeAddress": "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA"
},
"accounts": {
"testWallet": {
"public": "6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J",
"private": [
14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247,
22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101,
164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24,
100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109,
29, 134, 145, 132, 141
]
}
},
"addresses": {
"testToken": {
"address": "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ",
"name": "Solana Test Token",
"symbol": "SOLT",
"decimals": 6
},
"testNFT": {
"address": "BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna",
"name": "Not a PUNK🎸",
"symbol": "PUNK🎸",
"decimals": 0
},
"testNFT2": {
"address": "nftMANh29jbMboVnbYt1AUAWFP9N4Jnckr9Zeq85WUs",
"name": "Not a PUNK2🎸",
"symbol": "PUNK2🎸",
"decimals": 0
}
}
},
"gancheDefaults": [
{
"name": "0",
"public": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"private": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"
},
{
"name": "1",
"public": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"private": "0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1"
},
{
"name": "2",
"public": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
"private": "0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c"
},
{
"name": "3",
"public": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d",
"private": "0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913"
},
{
"name": "4",
"public": "0xd03ea8624C8C5987235048901fB614fDcA89b117",
"private": "0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743"
},
{
"name": "5",
"public": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC",
"private": "0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd"
},
{
"name": "6",
"public": "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9",
"private": "0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52"
},
{
"name": "7",
"public": "0x28a8746e75304c0780E011BEd21C72cD78cd535E",
"private": "0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3"
},
{
"name": "8",
"public": "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E",
"private": "0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4"
},
{
"name": "9",
"public": "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e",
"private": "0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773"
"2": {
"rpcUrlTilt": "http://eth-devnet:8545",
"rpcUrlLocal": "http://localhost:8545",
"rpcPort": "8545",
"contracts": {
"coreEmitterAddress": "000000000000000000000000c89ce4735882c9f0f0fe26686c53074e09b0d550",
"coreNativeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
"tokenBridgeEmitterAddress": "0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16",
"tokenBridgeNativeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
"nftBridgeEmitterAddress": "00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec",
"nftBridgeAddress": "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
},
"accounts": {
"testWallet": {
"public": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"private": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
"mnemonic": "myth like bonus scare over problem client lizard pioneer submit female collect"
}
],
"devnetGuardians": [
{
"name": "guardian-0",
"public": "0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
"private": "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0"
},
"addresses": {
"testToken": {
"address": "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A",
"name": "Ethereum Test Token",
"symbol": "TKN",
"decimals": 18
},
{
"name": "guardian-1",
"public": "0x88D7D8B32a9105d228100E72dFFe2Fae0705D31c",
"private": "c3b2e45c422a1602333a64078aeb42637370b0f48fe385f9cfa6ad54a8e0c47e"
"testNFT": {
"address": "0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66",
"name": "Not an APE 🐒",
"symbol": "APE🐒",
"decimals": 0
},
{
"name": "guardian-2",
"public": "0x58076F561CC62A47087B567C86f986426dFCD000",
"private": "9f790d3f08bc4b5cd910d4278f3deb406e57bb5e924906ccd52052bb078ccd47"
"testWETH": {
"address": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E",
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18
},
{
"name": "guardian-3",
"public": "0xBd6e9833490F8fA87c733A183CD076a6cBD29074",
"private": "b20cc49d6f2c82a5e6519015fc18aa3e562867f85f872c58f1277cfbd2a0c8e4"
},
{
"name": "guardian-4",
"public": "0xb853FCF0a5C78C1b56D15fCE7a154e6ebe9ED7a2",
"private": "eded5a2fdcb5bbbfa5b07f2a91393813420e7ac30a72fc935b6df36f8294b855"
},
{
"name": "guardian-5",
"public": "0xAF3503dBD2E37518ab04D7CE78b630F98b15b78a",
"private": "00d39587c3556f289677a837c7f3c0817cb7541ce6e38a243a4bdc761d534c5e"
},
{
"name": "guardian-6",
"public": "0x785632deA5609064803B1c8EA8bB2c77a6004Bd1",
"private": "da534d61a8da77b232f3a2cee55c0125e2b3e33a5cd8247f3fe9e72379445c3b"
},
{
"name": "guardian-7",
"public": "0x09a281a698C0F5BA31f158585B41F4f33659e54D",
"private": "cdbabfc2118eb00bc62c88845f3bbd03cb67a9e18a055101588ca9b36387006c"
},
{
"name": "guardian-8",
"public": "0x3178443AB76a60E21690DBfB17f7F59F09Ae3Ea1",
"private": "c83d36423820e7350428dc4abe645cb2904459b7d7128adefe16472fdac397ba"
},
{
"name": "guardian-9",
"public": "0x647ec26ae49b14060660504f4DA1c2059E1C5Ab6",
"private": "1cbf4e1388b81c9020500fefc83a7a81f707091bb899074db1bfce4537428112"
},
{
"name": "guardian-10",
"public": "0x810AC3D8E1258Bd2F004a94Ca0cd4c68Fc1C0611",
"private": "17646a6ba14a541957fc7112cc973c0b3f04fce59484a92c09bb45a0b57eb740"
},
{
"name": "guardian-11",
"public": "0x80610e96d645b12f47ae5cf4546b18538739e90F",
"private": "eb94ff04accbfc8195d44b45e7c7da4c6993b2fbbfc4ef166a7675a905df9891"
},
{
"name": "guardian-12",
"public": "0x2edb0D8530E31A218E72B9480202AcBaeB06178d",
"private": "053a6527124b309d914a47f5257a995e9b0ad17f14659f90ed42af5e6e262b6a"
},
{
"name": "guardian-13",
"public": "0xa78858e5e5c4705CdD4B668FFe3Be5bae4867c9D",
"private": "3fbf1e46f6da69e62aed5670f279e818889aa7d8f1beb7fd730770fd4f8ea3d7"
},
{
"name": "guardian-14",
"public": "0x5Efe3A05Efc62D60e1D19fAeB56A80223CDd3472",
"private": "53b05697596ba04067e40be8100c9194cbae59c90e7870997de57337497172e9"
},
{
"name": "guardian-15",
"public": "0xD791b7D32C05aBB1cc00b6381FA0c4928f0c56fC",
"private": "4e95cb2ff3f7d5e963631ad85c28b1b79cb370f21c67cbdd4c2ffb0bf664aa06"
},
{
"name": "guardian-16",
"public": "0x14Bc029B8809069093D712A3fd4DfAb31963597e",
"private": "01b8c448ce2c1d43cfc5938d3a57086f88e3dc43bb8b08028ecb7a7924f4676f"
},
{
"name": "guardian-17",
"public": "0x246Ab29FC6EBeDf2D392a51ab2Dc5C59d0902A03",
"private": "1db31a6ba3bcd54d2e8a64f8a2415064265d291593450c6eb7e9a6a986bd9400"
},
{
"name": "guardian-18",
"public": "0x132A84dFD920b35a3D0BA5f7A0635dF298F9033e",
"private": "70d8f1c9534a0ab61a020366b831a494057a289441c07be67e4288c44bc6cd5d"
"testGA": {
"address": "0x4cFB3F70BF6a80397C2e634e5bDd85BC0bb189EE",
"name": "Accountant Test Token",
"symbol": "GA",
"decimals": 18
}
]
}
},
"3": {
"rpcUrlTilt": "http://terra-terrad:1317",
"rpcUrlLocal": "http://localhost:1317",
"rpcPort": "1317",
"contracts": {
"coreEmitterAddress": "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
"coreNativeAddress": "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
"tokenBridgeEmitterAddress": "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4",
"tokenBridgeNativeAddress": "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4",
"nftBridgeEmitterAddress": "terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl",
"nftBridgeNativeAddress": "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"
},
"accounts": {
"testWallet": {
"public": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v",
"private": "",
"mnemonic": "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
}
},
"addresses": {
"testToken": {
"address": "terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh",
"name": "MOCK",
"symbol": "MCK",
"decimals": 6
},
"testNFT": {
"address": "terra18dt935pdcn2ka6l0syy5gt20wa48n3mktvdvjj",
"name": "MOCK",
"symbol": "MCK",
"decimals": 0
}
}
},
"4": {
"rpcUrlTilt": "http://eth-devnet2:8546",
"rpcUrlLocal": "http://localhost:8546",
"rpcPort": "8546",
"contracts": {
"coreEmitterAddress": "000000000000000000000000c89ce4735882c9f0f0fe26686c53074e09b0d550",
"coreNativeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
"tokenBridgeEmitterAddress": "0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16",
"tokenBridgeNativeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16",
"nftBridgeEmitterAddress": "00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec",
"nftBridgeAddress": "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
}
},
"8": {
"contracts": {
"tokenBridgeEmitterAddress": "8edf5b0e108c3a1a0a4b704cc89591f2ad8d50df24e991567e640ed720a94be2"
}
},
"15": {
"contracts": {
"tokenBridgeEmitterAddress": "e83c99874cb2d60921648a438606f5ffcf60c2e26ef13678b2e57fab3def6a30",
"nftBridgeEmitterAddress": "11aaad1c851095bf97ee1be9ed1161a47187aba8b71bf6821efac391d8ee95f7"
}
},
"18": {
"rpcUrlTilt": "http://terra2-terrad:1317",
"rpcUrlLocal": "http://localhost:1318",
"rpcPort": "1318",
"contracts": {
"coreEmitterAddress": "terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au",
"coreNativeAddress": "terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au",
"tokenBridgeEmitterAddress": "terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6",
"tokenBridgeNativeAddress": "terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6"
},
"accounts": {
"testWallet": {
"public": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v",
"private": "",
"mnemonic": "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
}
},
"addresses": {
"testToken": {
"address": "terra1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqynf7kp",
"name": "MOCK",
"symbol": "MCK",
"decimals": 6
}
}
},
"21": {
"contracts": {
"tokenBridgeEmitterAddress": "b99d994643e71bc6f460e33de1cc5167deedbac1374b9b2158c577d4c114037a"
}
},
"22": {
"contracts": {
"tokenBridgeEmitterAddress": "0000000000000000000000000000000000000000000000000000000000000001",
"nftBridgeEmitterAddress": "0000000000000000000000000000000000000000000000000000000000000002"
}
},
"3104": {
"rpcUrlTilt": "http://wormchain:1317",
"rpcUrlLocal": "http://localhost:1319",
"tendermintUrlTilt": "http://wormchain:26657",
"tendermintUrlLocal": "http://localhost:26659",
"rpcPort": "1317",
"tendermintPort": "26657",
"contracts": {
"coreEmitterAddress": "wormhole1ap5vgur5zlgys8whugfegnn43emka567dtq0jl",
"coreNativeAddress": "wormhole1ap5vgur5zlgys8whugfegnn43emka567dtq0jl",
"tokenBridgeEmitterAddress": "wormhole1zugu6cajc4z7ue29g9wnes9a5ep9cs7yu7rn3z",
"tokenBridgeNativeAddress": "wormhole1zugu6cajc4z7ue29g9wnes9a5ep9cs7yu7rn3z",
"accountingNativeAddress": "wormhole14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9srrg465"
},
"accounts": {
"wormchainNodeOfGuardian0": {
"address": "C10820983F33456CE7BEB3A046F5A83FA34F027D",
"addressBase64": "wQggmD8zRWznvrOgRvWoP6NPAn0=",
"addressWormhole": "000000000000000000000000c10820983f33456ce7beb3a046f5a83fa34f027d",
"public": "wormhole1cyyzpxplxdzkeea7kwsydadg87357qna3zg3tq",
"privateHex": "48d23cc417a30674e907a2403f109f082d92e197823d02e6a423c6aeb8e41204",
"cosmos.crypto.secp256k1.PubKey": "AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG",
"tendermint/PrivKeyEd25519": "DONGe0wxovG1ZuCQ1iMbyBCW/hG5UeKz6ZFfhdZYznRSC48Lc1nwhUwXzHtXfwAOY0mO3mhTy4CMwPeYFvBZ1A==",
"mnemonic": "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
},
"wormchainValidator0": {
"public": "wormholevaloper1cyyzpxplxdzkeea7kwsydadg87357qna87hzv8",
"tendermint/PubKeyEd25519": "fnfoo/C+i+Ng1J8vct6wfvrTS9JeNIG5UeO87ZHKMkY=",
"tendermint/PrivKeyEd25519": "Zb3gQZSd8qNMyXUQdKmeqM/SSYeVDD80S4XPEsCAgPN+d+ij8L6L42DUny9y3rB++tNL0l40gblR47ztkcoyRg=="
},
"wormchainNodeOfGuardian1": {
"address": "701C475B19A3F68D3FDEBF09591487FACEF2D636",
"addressWormhole": "000000000000000000000000701c475b19a3f68d3fdebf09591487facef2d636",
"addressBase64": "cBxHWxmj9o0/3r8JWRSH+s7y1jY=",
"public": "wormhole1wqwywkce50mg6077huy4j9y8lt80943ks5udzr",
"privateHex": "7095b73fa951fd117d54f3bca130b8088625db2d60d94d4f064791dc1a792b29",
"cosmos.crypto.secp256k1.PubKey": "ApJi/CY2RGyzA5cQtDwU9c+o7T8OE+SjrgcG5PwLMjTP",
"tendermint/PrivKeyEd25519": "TTdzb3XLJbSXP/5VhzPJCWysCDDH2hEXTqdvLI6RYk7rxPwzCXTprp2ZEfSCfQswYgUUQgO9JKzbAtfyeK2G1A==",
"mnemonic": "maple pudding enjoy pole real rabbit soft make square city wrestle area aisle dwarf spike voice over still post lend genius bitter exit shoot"
},
"wormchainValidator1": {
"public": "wormholevaloper1wqwywkce50mg6077huy4j9y8lt80943kxgr79y",
"tendermint/PubKeyEd25519": "Zcujkt1sXRWWLfhgxLAm/Q+ioLn4wFim0OnGPLlCG0I=",
"tendermint/PrivKeyEd25519": "SGWIYI3BgC/dxNOk1gYx6LpChAKqWGtAfZSx0SDFWuhly6OS3WxdFZYt+GDEsCb9D6KgufjAWKbQ6cY8uUIbQg=="
}
},
"addresses": {
"native": {
"address": "uworm",
"addressWormhole": "010c0ded78f1b69ec7b79b9ee592fbbcacebc97db1c695220a833135bfa74824",
"denom": "uworm",
"name": "worm",
"symbol": "worm",
"decimals": 0
},
"testToken": {
"address": "wormhole1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqhnev3f",
"addressWormhole": "003f822e9066cfea09b9ce1247e8f79a86a24dda2d8b3d76a608ae7583220411",
"name": "MOCK",
"symbol": "MCK",
"decimals": 6
}
}
}
},
"gancheDefaults": [
{
"name": "0",
"public": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"private": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"
},
{
"name": "1",
"public": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"private": "0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1"
},
{
"name": "2",
"public": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
"private": "0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c"
},
{
"name": "3",
"public": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d",
"private": "0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913"
},
{
"name": "4",
"public": "0xd03ea8624C8C5987235048901fB614fDcA89b117",
"private": "0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743"
},
{
"name": "5",
"public": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC",
"private": "0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd"
},
{
"name": "6",
"public": "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9",
"private": "0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52"
},
{
"name": "7",
"public": "0x28a8746e75304c0780E011BEd21C72cD78cd535E",
"private": "0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3"
},
{
"name": "8",
"public": "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E",
"private": "0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4"
},
{
"name": "9",
"public": "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e",
"private": "0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773"
},
{
"name": "10",
"public": "0x610bb1573d1046fcb8a70bbbd395754cd57c2b60",
"private": "0x77c5495fbb039eed474fc940f29955ed0531693cc9212911efd35dff0373153f"
}
],
"devnetGuardians": [
{
"name": "guardian-0",
"public": "0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
"private": "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0"
},
{
"name": "guardian-1",
"public": "0x88D7D8B32a9105d228100E72dFFe2Fae0705D31c",
"private": "c3b2e45c422a1602333a64078aeb42637370b0f48fe385f9cfa6ad54a8e0c47e"
},
{
"name": "guardian-2",
"public": "0x58076F561CC62A47087B567C86f986426dFCD000",
"private": "9f790d3f08bc4b5cd910d4278f3deb406e57bb5e924906ccd52052bb078ccd47"
},
{
"name": "guardian-3",
"public": "0xBd6e9833490F8fA87c733A183CD076a6cBD29074",
"private": "b20cc49d6f2c82a5e6519015fc18aa3e562867f85f872c58f1277cfbd2a0c8e4"
},
{
"name": "guardian-4",
"public": "0xb853FCF0a5C78C1b56D15fCE7a154e6ebe9ED7a2",
"private": "eded5a2fdcb5bbbfa5b07f2a91393813420e7ac30a72fc935b6df36f8294b855"
},
{
"name": "guardian-5",
"public": "0xAF3503dBD2E37518ab04D7CE78b630F98b15b78a",
"private": "00d39587c3556f289677a837c7f3c0817cb7541ce6e38a243a4bdc761d534c5e"
},
{
"name": "guardian-6",
"public": "0x785632deA5609064803B1c8EA8bB2c77a6004Bd1",
"private": "da534d61a8da77b232f3a2cee55c0125e2b3e33a5cd8247f3fe9e72379445c3b"
},
{
"name": "guardian-7",
"public": "0x09a281a698C0F5BA31f158585B41F4f33659e54D",
"private": "cdbabfc2118eb00bc62c88845f3bbd03cb67a9e18a055101588ca9b36387006c"
},
{
"name": "guardian-8",
"public": "0x3178443AB76a60E21690DBfB17f7F59F09Ae3Ea1",
"private": "c83d36423820e7350428dc4abe645cb2904459b7d7128adefe16472fdac397ba"
},
{
"name": "guardian-9",
"public": "0x647ec26ae49b14060660504f4DA1c2059E1C5Ab6",
"private": "1cbf4e1388b81c9020500fefc83a7a81f707091bb899074db1bfce4537428112"
},
{
"name": "guardian-10",
"public": "0x810AC3D8E1258Bd2F004a94Ca0cd4c68Fc1C0611",
"private": "17646a6ba14a541957fc7112cc973c0b3f04fce59484a92c09bb45a0b57eb740"
},
{
"name": "guardian-11",
"public": "0x80610e96d645b12f47ae5cf4546b18538739e90F",
"private": "eb94ff04accbfc8195d44b45e7c7da4c6993b2fbbfc4ef166a7675a905df9891"
},
{
"name": "guardian-12",
"public": "0x2edb0D8530E31A218E72B9480202AcBaeB06178d",
"private": "053a6527124b309d914a47f5257a995e9b0ad17f14659f90ed42af5e6e262b6a"
},
{
"name": "guardian-13",
"public": "0xa78858e5e5c4705CdD4B668FFe3Be5bae4867c9D",
"private": "3fbf1e46f6da69e62aed5670f279e818889aa7d8f1beb7fd730770fd4f8ea3d7"
},
{
"name": "guardian-14",
"public": "0x5Efe3A05Efc62D60e1D19fAeB56A80223CDd3472",
"private": "53b05697596ba04067e40be8100c9194cbae59c90e7870997de57337497172e9"
},
{
"name": "guardian-15",
"public": "0xD791b7D32C05aBB1cc00b6381FA0c4928f0c56fC",
"private": "4e95cb2ff3f7d5e963631ad85c28b1b79cb370f21c67cbdd4c2ffb0bf664aa06"
},
{
"name": "guardian-16",
"public": "0x14Bc029B8809069093D712A3fd4DfAb31963597e",
"private": "01b8c448ce2c1d43cfc5938d3a57086f88e3dc43bb8b08028ecb7a7924f4676f"
},
{
"name": "guardian-17",
"public": "0x246Ab29FC6EBeDf2D392a51ab2Dc5C59d0902A03",
"private": "1db31a6ba3bcd54d2e8a64f8a2415064265d291593450c6eb7e9a6a986bd9400"
},
{
"name": "guardian-18",
"public": "0x132A84dFD920b35a3D0BA5f7A0635dF298F9033e",
"private": "70d8f1c9534a0ab61a020366b831a494057a289441c07be67e4288c44bc6cd5d"
}
]
}

View File

@ -10,7 +10,7 @@ addressesJson="./scripts/devnet-consts.json"
# working files for accumulating state
envFile="./scripts/.env.hex" # for generic hex data, for solana, terra, etc
ethFile="./scripts/.env.0x" # for "0x" prefixed data, for ethereum scripts
ethFile="./scripts/.env.0x" # for "0x" prefixed data, for ethereum scripts
# copy the eth defaults so we can override just the things we need
cp ./ethereum/.env.test $ethFile
@ -59,7 +59,7 @@ guardiansPublicEth=$(jq -c --argjson lastIndex $numGuardians '.devnetGuardians[:
# guardiansPublicHex does not have a leading "0x", just hex strings.
guardiansPublicHex=$(jq -c --argjson lastIndex $numGuardians '.devnetGuardians[:$lastIndex] | [.[].public[2:]]' $addressesJson)
# also make a CSV string of the hex addresses, so the client scripts that need that format don't have to.
guardiansPublicHexCSV=$(echo ${guardiansPublicHex} | jq --raw-output -c '. | join(",")')
guardiansPublicHexCSV=$(echo ${guardiansPublicHex} | jq --raw-output -c '. | join(",")')
# write the lists of addresses to the env files
initSigners="INIT_SIGNERS"
@ -73,7 +73,7 @@ echo "generating guardian set keys"
# create an array of strings containing the private keys of the devnet guardians in the guardianset
guardiansPrivate=$(jq -c --argjson lastIndex $numGuardians '.devnetGuardians[:$lastIndex] | [.[].private]' $addressesJson)
# create a CSV string with the private keys of the guardians in the guardianset, that will be used to create registration VAAs
guardiansPrivateCSV=$( echo ${guardiansPrivate} | jq --raw-output -c '. | join(",")')
guardiansPrivateCSV=$(echo ${guardiansPrivate} | jq --raw-output -c '. | join(",")')
# write the lists of keys to the env files
upsert_env_file $ethFile "INIT_SIGNERS_KEYS_JSON" $guardiansPrivate
@ -90,8 +90,9 @@ bscTokenBridge=$(jq --raw-output '.chains."4".contracts.tokenBridgeEmitterAddres
algoTokenBridge=$(jq --raw-output '.chains."8".contracts.tokenBridgeEmitterAddress' $addressesJson)
nearTokenBridge=$(jq --raw-output '.chains."15".contracts.tokenBridgeEmitterAddress' $addressesJson)
terra2TokenBridge=$(jq --raw-output '.chains."18".contracts.tokenBridgeEmitterAddress' $addressesJson)
wormchainTokenBridge=$(jq --raw-output '.chains."3104".contracts.tokenBridgeEmitterAddress' $addressesJson)
suiTokenBridge=$(jq --raw-output '.chains."21".contracts.tokenBridgeEmitterAddress' $addressesJson)
aptosTokenBridge=$(jq --raw-output '.chains."22".contracts.tokenBridgeEmitterAddress' $addressesJson)
wormchainTokenBridge=$(jq --raw-output '.chains."3104".contracts.tokenBridgeEmitterAddress' $addressesJson)
solNFTBridge=$(jq --raw-output '.chains."1".contracts.nftBridgeEmitterAddress' $addressesJson)
ethNFTBridge=$(jq --raw-output '.chains."2".contracts.nftBridgeEmitterAddress' $addressesJson)
@ -102,14 +103,15 @@ aptosNFTBridge=$(jq --raw-output '.chains."22".contracts.nftBridgeEmitterAddress
# 4) create token bridge registration VAAs
# invoke CLI commands to create registration VAAs
solTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c solana -a ${solTokenBridge} -g ${guardiansPrivateCSV})
ethTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c ethereum -a ${ethTokenBridge} -g ${guardiansPrivateCSV} )
ethTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c ethereum -a ${ethTokenBridge} -g ${guardiansPrivateCSV})
terraTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c terra -a ${terraTokenBridge} -g ${guardiansPrivateCSV})
bscTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c bsc -a ${bscTokenBridge} -g ${guardiansPrivateCSV})
algoTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c algorand -a ${algoTokenBridge} -g ${guardiansPrivateCSV})
nearTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c near -a ${nearTokenBridge} -g ${guardiansPrivateCSV})
terra2TokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c terra2 -a ${terra2TokenBridge} -g ${guardiansPrivateCSV})
wormchainTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c wormchain -a ${wormchainTokenBridge} -g ${guardiansPrivateCSV})
suiTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c sui -a ${suiTokenBridge} -g ${guardiansPrivateCSV})
aptosTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c aptos -a ${aptosTokenBridge} -g ${guardiansPrivateCSV})
wormchainTokenBridgeVAA=$(node ./clients/js/build/main.js generate registration -m TokenBridge -c wormchain -a ${wormchainTokenBridge} -g ${guardiansPrivateCSV})
# 5) create nft bridge registration VAAs
@ -131,8 +133,9 @@ bscTokenBridge="REGISTER_BSC_TOKEN_BRIDGE_VAA"
algoTokenBridge="REGISTER_ALGO_TOKEN_BRIDGE_VAA"
terra2TokenBridge="REGISTER_TERRA2_TOKEN_BRIDGE_VAA"
nearTokenBridge="REGISTER_NEAR_TOKEN_BRIDGE_VAA"
wormchainTokenBridge="REGISTER_WORMCHAIN_TOKEN_BRIDGE_VAA"
suiTokenBridge="REGISTER_SUI_TOKEN_BRIDGE_VAA"
aptosTokenBridge="REGISTER_APTOS_TOKEN_BRIDGE_VAA"
wormchainTokenBridge="REGISTER_WORMCHAIN_TOKEN_BRIDGE_VAA"
solNFTBridge="REGISTER_SOL_NFT_BRIDGE_VAA"
ethNFTBridge="REGISTER_ETH_NFT_BRIDGE_VAA"
@ -140,7 +143,6 @@ terraNFTBridge="REGISTER_TERRA_NFT_BRIDGE_VAA"
nearNFTBridge="REGISTER_NEAR_NFT_BRIDGE_VAA"
aptosNFTBridge="REGISTER_APTOS_NFT_BRIDGE_VAA"
# solana token bridge
upsert_env_file $ethFile $solTokenBridge $solTokenBridgeVAA
upsert_env_file $envFile $solTokenBridge $solTokenBridgeVAA
@ -148,7 +150,6 @@ upsert_env_file $envFile $solTokenBridge $solTokenBridgeVAA
upsert_env_file $ethFile $solNFTBridge $solNFTBridgeVAA
upsert_env_file $envFile $solNFTBridge $solNFTBridgeVAA
# ethereum token bridge
upsert_env_file $ethFile $ethTokenBridge $ethTokenBridgeVAA
upsert_env_file $envFile $ethTokenBridge $ethTokenBridgeVAA
@ -156,7 +157,6 @@ upsert_env_file $envFile $ethTokenBridge $ethTokenBridgeVAA
upsert_env_file $ethFile $ethNFTBridge $ethNFTBridgeVAA
upsert_env_file $envFile $ethNFTBridge $ethNFTBridgeVAA
# terra token bridge
upsert_env_file $ethFile $terraTokenBridge $terraTokenBridgeVAA
upsert_env_file $envFile $terraTokenBridge $terraTokenBridgeVAA
@ -164,7 +164,6 @@ upsert_env_file $envFile $terraTokenBridge $terraTokenBridgeVAA
upsert_env_file $ethFile $terraNFTBridge $terraNFTBridgeVAA
upsert_env_file $envFile $terraNFTBridge $terraNFTBridgeVAA
# bsc token bridge
upsert_env_file $ethFile $bscTokenBridge $bscTokenBridgeVAA
upsert_env_file $envFile $bscTokenBridge $bscTokenBridgeVAA
@ -177,22 +176,24 @@ upsert_env_file $envFile $algoTokenBridge $algoTokenBridgeVAA
upsert_env_file $ethFile $terra2TokenBridge $terra2TokenBridgeVAA
upsert_env_file $envFile $terra2TokenBridge $terra2TokenBridgeVAA
# aptos token bridge
upsert_env_file $ethFile $aptosTokenBridge $aptosTokenBridgeVAA
upsert_env_file $envFile $aptosTokenBridge $aptosTokenBridgeVAA
# aptos nft bridge
upsert_env_file $ethFile $aptosNFTBridge $aptosNFTBridgeVAA
upsert_env_file $envFile $aptosNFTBridge $aptosNFTBridgeVAA
# near token bridge
upsert_env_file $ethFile $nearTokenBridge $nearTokenBridgeVAA
upsert_env_file $envFile $nearTokenBridge $nearTokenBridgeVAA
# near nft bridge
upsert_env_file $ethFile $nearNFTBridge $nearNFTBridgeVAA
upsert_env_file $envFile $nearNFTBridge $nearNFTBridgeVAA
# sui token bridge
upsert_env_file $ethFile $suiTokenBridge $suiTokenBridgeVAA
upsert_env_file $envFile $suiTokenBridge $suiTokenBridgeVAA
# aptos token bridge
upsert_env_file $ethFile $aptosTokenBridge $aptosTokenBridgeVAA
upsert_env_file $envFile $aptosTokenBridge $aptosTokenBridgeVAA
# aptos nft bridge
upsert_env_file $ethFile $aptosNFTBridge $aptosNFTBridgeVAA
upsert_env_file $envFile $aptosNFTBridge $aptosNFTBridgeVAA
# wormchain token bridge
upsert_env_file $ethFile $wormchainTokenBridge $wormchainTokenBridgeVAA
upsert_env_file $envFile $wormchainTokenBridge $wormchainTokenBridgeVAA
@ -212,6 +213,8 @@ paths=(
./solana/.env
./terra/tools/.env
./cosmwasm/deployment/terra2/tools/.env
./sui/.env
./aptos/.env
./wormchain/contracts/tools/.env
)

View File

@ -16,6 +16,7 @@ var knownDevnetTokenbridgeEmitters = map[vaa.ChainID]string{
vaa.ChainIDBSC: "0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16",
vaa.ChainIDAlgorand: "8edf5b0e108c3a1a0a4b704cc89591f2ad8d50df24e991567e640ed720a94be2",
vaa.ChainIDWormchain: "0000000000000000000000001711cd63b2c545ee6545415d3cc0bda6425c43c4",
vaa.ChainIDSui: "b99d994643e71bc6f460e33de1cc5167deedbac1374b9b2158c577d4c114037a",
}
// KnownDevnetNFTBridgeEmitters is a map of known NFT emitters used during development.

316
sdk/js/package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@certusone/wormhole-sdk",
"version": "0.9.13",
"version": "0.9.14",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@certusone/wormhole-sdk",
"version": "0.9.13",
"version": "0.9.14",
"license": "Apache-2.0",
"dependencies": {
"@certusone/wormhole-sdk-proto-web": "0.0.6",
@ -15,6 +15,7 @@
"@injectivelabs/networks": "1.10.7",
"@injectivelabs/sdk-ts": "1.10.47",
"@injectivelabs/utils": "1.10.5",
"@mysten/sui.js": "0.32.2",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.3.5",
"@solana/web3.js": "^1.66.2",
@ -3026,6 +3027,144 @@
"rlp": "^2.2.3"
}
},
"node_modules/@mysten/bcs": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-0.7.1.tgz",
"integrity": "sha512-wFPb8bkhwrbiStfZMV5rFM7J+umpke59/dNjDp+UYJKykNlW23LCk2ePyEUvGdb62HGJM1jyOJ8g4egE3OmdKA==",
"dependencies": {
"bs58": "^5.0.0"
}
},
"node_modules/@mysten/bcs/node_modules/base-x": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
},
"node_modules/@mysten/bcs/node_modules/bs58": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
"dependencies": {
"base-x": "^4.0.0"
}
},
"node_modules/@mysten/sui.js": {
"version": "0.32.2",
"resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
"integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
"dependencies": {
"@mysten/bcs": "0.7.1",
"@noble/curves": "^1.0.0",
"@noble/hashes": "^1.3.0",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@suchipi/femver": "^1.0.0",
"jayson": "^4.0.0",
"rpc-websockets": "^7.5.1",
"superstruct": "^1.0.3",
"tweetnacl": "^1.0.3"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@mysten/sui.js/node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@mysten/sui.js/node_modules/@scure/bip39": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@mysten/sui.js/node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"node_modules/@mysten/sui.js/node_modules/jayson": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
"integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
"dependencies": {
"@types/connect": "^3.4.33",
"@types/node": "^12.12.54",
"@types/ws": "^7.4.4",
"commander": "^2.20.3",
"delay": "^5.0.0",
"es6-promisify": "^5.0.0",
"eyes": "^0.1.8",
"isomorphic-ws": "^4.0.1",
"json-stringify-safe": "^5.0.1",
"JSONStream": "^1.3.5",
"uuid": "^8.3.2",
"ws": "^7.4.5"
},
"bin": {
"jayson": "bin/jayson.js"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@mysten/sui.js/node_modules/superstruct": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@mysten/sui.js/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@noble/curves": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
"integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "1.3.0"
}
},
"node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@noble/ed25519": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz",
@ -3236,6 +3375,33 @@
}
]
},
"node_modules/@scure/bip32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
"integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/curves": "~1.0.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@scure/bip32/node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@scure/bip39": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz",
@ -3402,6 +3568,11 @@
"node": ">=12.20.0"
}
},
"node_modules/@suchipi/femver": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz",
"integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg=="
},
"node_modules/@szmarczak/http-timer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
@ -8595,7 +8766,6 @@
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz",
"integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
@ -8958,7 +9128,6 @@
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz",
"integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
@ -15049,9 +15218,9 @@
}
},
"node_modules/rpc-websockets": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz",
"integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==",
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.1.tgz",
"integrity": "sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"eventemitter3": "^4.0.7",
@ -19986,6 +20155,111 @@
}
}
},
"@mysten/bcs": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-0.7.1.tgz",
"integrity": "sha512-wFPb8bkhwrbiStfZMV5rFM7J+umpke59/dNjDp+UYJKykNlW23LCk2ePyEUvGdb62HGJM1jyOJ8g4egE3OmdKA==",
"requires": {
"bs58": "^5.0.0"
},
"dependencies": {
"base-x": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
},
"bs58": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
"requires": {
"base-x": "^4.0.0"
}
}
}
},
"@mysten/sui.js": {
"version": "0.32.2",
"resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
"integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
"requires": {
"@mysten/bcs": "0.7.1",
"@noble/curves": "^1.0.0",
"@noble/hashes": "^1.3.0",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@suchipi/femver": "^1.0.0",
"jayson": "^4.0.0",
"rpc-websockets": "^7.5.1",
"superstruct": "^1.0.3",
"tweetnacl": "^1.0.3"
},
"dependencies": {
"@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
},
"@scure/bip39": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz",
"integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==",
"requires": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
}
},
"@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"jayson": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
"integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
"requires": {
"@types/connect": "^3.4.33",
"@types/node": "^12.12.54",
"@types/ws": "^7.4.4",
"commander": "^2.20.3",
"delay": "^5.0.0",
"es6-promisify": "^5.0.0",
"eyes": "^0.1.8",
"isomorphic-ws": "^4.0.1",
"json-stringify-safe": "^5.0.1",
"JSONStream": "^1.3.5",
"uuid": "^8.3.2",
"ws": "^7.4.5"
}
},
"superstruct": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
"@noble/curves": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz",
"integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==",
"requires": {
"@noble/hashes": "1.3.0"
},
"dependencies": {
"@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
}
}
},
"@noble/ed25519": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz",
@ -20168,6 +20442,23 @@
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="
},
"@scure/bip32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz",
"integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==",
"requires": {
"@noble/curves": "~1.0.0",
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
},
"dependencies": {
"@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
}
}
},
"@scure/bip39": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz",
@ -20282,6 +20573,11 @@
"superstruct": "^0.14.2"
}
},
"@suchipi/femver": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz",
"integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg=="
},
"@szmarczak/http-timer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
@ -29573,9 +29869,9 @@
}
},
"rpc-websockets": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz",
"integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==",
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.1.tgz",
"integrity": "sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==",
"requires": {
"@babel/runtime": "^7.17.2",
"bufferutil": "^4.0.1",

View File

@ -72,6 +72,7 @@
"@injectivelabs/networks": "1.10.7",
"@injectivelabs/sdk-ts": "1.10.47",
"@injectivelabs/utils": "1.10.5",
"@mysten/sui.js": "0.32.2",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.3.5",
"@solana/web3.js": "^1.66.2",

View File

@ -5,6 +5,7 @@ import { AptosClient, Types } from "aptos";
import { BigNumber, ContractReceipt } from "ethers";
import { FinalExecutionOutcome } from "near-api-js/lib/providers";
import { Implementation__factory } from "../ethers-contracts";
import { SuiTransactionBlockResponse } from "@mysten/sui.js";
export function parseSequenceFromLogEth(
receipt: ContractReceipt,
@ -180,3 +181,15 @@ export function parseSequenceFromLogAptos(
return null;
}
export function parseSequenceFromLogSui(
originalCoreBridgePackageId: string,
response: SuiTransactionBlockResponse
): string | null {
const event = response.events?.find(
(e) =>
e.type ===
`${originalCoreBridgePackageId}::publish_message::WormholeMessage`
);
return event?.parsedJson?.sequence || null;
}

View File

@ -40,7 +40,7 @@ import {
tryNativeToUint8Array,
uint8ArrayToHex,
} from "..";
import { CLUSTER } from "../token_bridge/__tests__/consts";
import { CLUSTER } from "../token_bridge/__tests__/utils/consts";
import algosdk, {
Account,
Algodv2,

View File

@ -15,5 +15,6 @@ export * as bridge from "./bridge";
export * as token_bridge from "./token_bridge";
export * as nft_bridge from "./nft_bridge";
export * as algorand from "./algorand";
export * as sui from "./sui";
export { postVaaSolana, postVaaSolanaWithRetry } from "./solana";

View File

@ -44,7 +44,7 @@ import {
SOLANA_HOST,
SOLANA_PRIVATE_KEY2,
TEST_SOLANA_TOKEN3,
} from "./consts";
} from "./utils/consts";
import {
deployTestNftOnAptos,
deployTestNftOnEthereum,

View File

@ -31,7 +31,7 @@ import {
SOLANA_HOST,
SOLANA_PRIVATE_KEY,
TEST_SOLANA_TOKEN,
} from "./consts";
} from "./utils/consts";
import { getSignedVaaEthereum, getSignedVaaSolana } from "./utils/getSignedVaa";
jest.setTimeout(60000);

View File

@ -19,7 +19,7 @@ import {
CHAIN_ID_SOLANA,
CONTRACTS,
} from "../../../utils";
import { WORMHOLE_RPC_HOSTS } from "../consts";
import { WORMHOLE_RPC_HOSTS } from "./consts";
// TODO(aki): implement getEmitterAddressAptos and sub here
export async function getSignedVaaAptos(

7
sdk/js/src/sui/error.ts Normal file
View File

@ -0,0 +1,7 @@
export class SuiRpcValidationError extends Error {
constructor(response: any) {
super(
`Sui RPC returned an unexpected response: ${JSON.stringify(response)}`
);
}
}

2
sdk/js/src/sui/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./publish";
export * from "./utils";

84
sdk/js/src/sui/publish.ts Normal file
View File

@ -0,0 +1,84 @@
import {
fromB64,
JsonRpcProvider,
normalizeSuiObjectId,
TransactionBlock,
} from "@mysten/sui.js";
import { SuiBuildOutput } from "./types";
import { getOriginalPackageId, getPackageId } from "./utils";
export const publishCoin = async (
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
decimals: number,
signerAddress: string
) => {
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
const build = await getCoinBuildOutput(
provider,
coreBridgePackageId,
tokenBridgePackageId,
tokenBridgeStateObjectId,
decimals
);
return publishPackage(build, signerAddress);
};
export const getCoinBuildOutput = async (
provider: JsonRpcProvider,
coreBridgePackageId: string,
tokenBridgePackageId: string,
tokenBridgeStateObjectId: string,
decimals: number
): Promise<SuiBuildOutput> => {
// Decimals is capped at 8
decimals = Math.min(decimals, 8);
// Construct bytecode, parametrized by token bridge package ID and decimals
const strippedTokenBridgePackageId = (
await getOriginalPackageId(provider, tokenBridgeStateObjectId)
)?.replace("0x", "");
if (!strippedTokenBridgePackageId) {
throw new Error(
`Original token bridge package ID not found for object ID ${tokenBridgeStateObjectId}`
);
}
const bytecodeHex =
"a11ceb0b060000000901000a020a14031e1704350405392d07669f01088502600ae502050cea02160004010b010c0205020d000002000201020003030c020001000104020700000700010001090801010c020a050600030803040202000302010702080007080100020800080303090002070801010b020209000901010608010105010b0202080008030209000504434f494e095478436f6e7465787408565f5f305f325f3011577261707065644173736574536574757004636f696e0e6372656174655f777261707065640b64756d6d795f6669656c6404696e697414707265706172655f726567697374726174696f6e0f7075626c69635f7472616e736665720673656e646572087472616e736665720a74785f636f6e746578740f76657273696f6e5f636f6e74726f6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002" +
strippedTokenBridgePackageId +
"00020106010000000001090b0031" +
decimals.toString(16).padStart(2, "0") +
"0a0138000b012e110238010200";
const bytecode = Buffer.from(bytecodeHex, "hex").toString("base64");
return {
modules: [bytecode],
dependencies: ["0x1", "0x2", tokenBridgePackageId, coreBridgePackageId].map(
(d) => normalizeSuiObjectId(d)
),
};
};
export const publishPackage = async (
buildOutput: SuiBuildOutput,
signerAddress: string
): Promise<TransactionBlock> => {
// Publish contracts
const tx = new TransactionBlock();
const [upgradeCap] = tx.publish({
modules: buildOutput.modules.map((m) => Array.from(fromB64(m))),
dependencies: buildOutput.dependencies.map((d) => normalizeSuiObjectId(d)),
});
// Transfer upgrade capability to recipient
tx.transferObjects([upgradeCap], tx.pure(signerAddress));
return tx;
};

20
sdk/js/src/sui/types.ts Normal file
View File

@ -0,0 +1,20 @@
export type ParsedMoveToml = {
name: string;
rows: { key: string; value: string }[];
}[];
export type SuiBuildOutput = {
modules: string[];
dependencies: string[];
};
export type SuiError = {
code: number;
message: string;
data: any;
};
export type SuiCoinObject = {
coinType: string;
coinObjectId: string;
};

View File

@ -0,0 +1,11 @@
import { unnormalizeSuiAddress } from "./utils";
describe("Sui utils tests", () => {
test("Test unnormalizeSuiAddress", () => {
const initial =
"0x09bc8dd67bbbf59a43a9081d7166f9b41740c3a8ae868c4902d30eb247292ba4::coin::COIN";
const expected =
"0x9bc8dd67bbbf59a43a9081d7166f9b41740c3a8ae868c4902d30eb247292ba4::coin::COIN";
expect(unnormalizeSuiAddress(initial)).toBe(expected);
});
});

414
sdk/js/src/sui/utils.ts Normal file
View File

@ -0,0 +1,414 @@
import {
getObjectType,
isValidSuiAddress as isValidFullSuiAddress,
JsonRpcProvider,
normalizeSuiAddress,
PaginatedObjectsResponse,
RawSigner,
SuiObjectResponse,
SuiTransactionBlockResponse,
TransactionBlock,
} from "@mysten/sui.js";
import { ensureHexPrefix } from "../utils";
import { SuiRpcValidationError } from "./error";
import { SuiError } from "./types";
const UPGRADE_CAP_TYPE = "0x2::package::UpgradeCap";
export const executeTransactionBlock = async (
signer: RawSigner,
transactionBlock: TransactionBlock
): Promise<SuiTransactionBlockResponse> => {
// Let caller handle parsing and logging info
transactionBlock.setGasBudget(100000000);
return signer.signAndExecuteTransactionBlock({
transactionBlock,
options: {
showInput: true,
showEffects: true,
showEvents: true,
showObjectChanges: true,
},
});
};
// TODO: can we pass in the latest core bridge package Id after an upgrade?
// or do we have to use the first one?
// this is the same type that the guardian will look for
export const getEmitterAddressAndSequenceFromResponseSui = (
originalCoreBridgePackageId: string,
response: SuiTransactionBlockResponse
): { emitterAddress: string; sequence: string } => {
const wormholeMessageEventType = `${originalCoreBridgePackageId}::publish_message::WormholeMessage`;
const event = response.events?.find(
(e) => e.type === wormholeMessageEventType
);
if (event === undefined) {
throw new Error(`${wormholeMessageEventType} event type not found`);
}
const { sender, sequence } = event.parsedJson || {};
if (sender === undefined || sequence === undefined) {
throw new Error("Can't find sender or sequence");
}
return { emitterAddress: sender.substring(2), sequence };
};
export const getFieldsFromObjectResponse = (object: SuiObjectResponse) => {
const content = object.data?.content;
return content && content.dataType === "moveObject" ? content.fields : null;
};
export const getInnerType = (type: string): string | null => {
if (!type) return null;
const match = type.match(/<(.*)>/);
if (!match || !isValidSuiType(match[1])) {
return null;
}
return match[1];
};
export const getObjectFields = async (
provider: JsonRpcProvider,
objectId: string
): Promise<Record<string, any> | null> => {
if (!isValidSuiAddress(objectId)) {
throw new Error(`Invalid object ID: ${objectId}`);
}
const res = await provider.getObject({
id: objectId,
options: {
showContent: true,
},
});
return getFieldsFromObjectResponse(res);
};
export const getOriginalPackageId = async (
provider: JsonRpcProvider,
stateObjectId: string
) => {
return getObjectType(
await provider.getObject({
id: stateObjectId,
options: { showContent: true },
})
)?.split("::")[0];
};
export const getOwnedObjectId = async (
provider: JsonRpcProvider,
owner: string,
type: string
): Promise<string | null> => {
// Upgrade caps are a special case
if (normalizeSuiType(type) === normalizeSuiType(UPGRADE_CAP_TYPE)) {
throw new Error(
"`getOwnedObjectId` should not be used to get the object ID of an `UpgradeCap`. Use `getUpgradeCapObjectId` instead."
);
}
try {
const res = await provider.getOwnedObjects({
owner,
filter: { StructType: type },
options: {
showContent: true,
},
});
if (!res || !res.data) {
throw new SuiRpcValidationError(res);
}
const objects = res.data.filter((o) => o.data?.objectId);
if (objects.length === 1) {
return objects[0].data?.objectId ?? null;
} else if (objects.length > 1) {
const objectsStr = JSON.stringify(objects, null, 2);
throw new Error(
`Found multiple objects owned by ${owner} of type ${type}. This may mean that we've received an unexpected response from the Sui RPC and \`worm\` logic needs to be updated to handle this. Objects: ${objectsStr}`
);
} else {
return null;
}
} catch (error) {
// Handle 504 error by using findOwnedObjectByType method
const is504HttpError = `${error}`.includes("504 Gateway Time-out");
if (error && is504HttpError) {
return getOwnedObjectIdPaginated(provider, owner, type);
} else {
throw error;
}
}
};
export const getOwnedObjectIdPaginated = async (
provider: JsonRpcProvider,
owner: string,
type: string,
cursor?: string
): Promise<string | null> => {
const res: PaginatedObjectsResponse = await provider.getOwnedObjects({
owner,
filter: undefined, // Filter must be undefined to avoid 504 responses
cursor: cursor || undefined,
options: {
showType: true,
},
});
if (!res || !res.data) {
throw new SuiRpcValidationError(res);
}
const object = res.data.find((d) => d.data?.type === type);
if (!object && res.hasNextPage) {
return getOwnedObjectIdPaginated(
provider,
owner,
type,
res.nextCursor as string
);
} else if (!object && !res.hasNextPage) {
return null;
} else {
return object?.data?.objectId ?? null;
}
};
export const getPackageId = async (
provider: JsonRpcProvider,
objectId: string
): Promise<string> => {
const fields = await getObjectFields(provider, objectId);
if (fields && "upgrade_cap" in fields) {
return fields.upgrade_cap.fields.package;
}
throw new Error("upgrade_cap not found");
};
export const getPackageIdFromType = (type: string): string | null => {
if (!isValidSuiType(type)) return null;
const packageId = type.split("::")[0];
if (!isValidSuiAddress(packageId)) return null;
return packageId;
};
export const getTableKeyType = (tableType: string): string | null => {
if (!tableType) return null;
const match = tableType.match(/0x2::table::Table<(.*)>/);
if (!match) return null;
const [keyType] = match[1].split(",");
if (!isValidSuiType(keyType)) return null;
return keyType;
};
export const getTokenCoinType = async (
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string,
tokenAddress: Uint8Array,
tokenChain: number
): Promise<string | null> => {
const tokenBridgeStateFields = await getObjectFields(
provider,
tokenBridgeStateObjectId
);
if (!tokenBridgeStateFields) {
throw new Error("Unable to fetch object fields from token bridge state");
}
const coinTypes = tokenBridgeStateFields?.token_registry?.fields?.coin_types;
const coinTypesObjectId = coinTypes?.fields?.id?.id;
if (!coinTypesObjectId) {
throw new Error("Unable to fetch coin types");
}
const keyType = getTableKeyType(coinTypes?.type);
if (!keyType) {
throw new Error("Unable to get key type");
}
const response = await provider.getDynamicFieldObject({
parentId: coinTypesObjectId,
name: {
type: keyType,
value: {
addr: [...tokenAddress],
chain: tokenChain,
},
},
});
if (response.error) {
if (response.error.code === "dynamicFieldNotFound") {
return null;
}
throw new Error(
`Unexpected getDynamicFieldObject response ${response.error}`
);
}
const fields = getFieldsFromObjectResponse(response);
return fields?.value
? unnormalizeSuiAddress(ensureHexPrefix(fields.value))
: null;
};
export const getTokenFromTokenRegistry = async (
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string,
tokenType: string
): Promise<SuiObjectResponse> => {
if (!isValidSuiType(tokenType)) {
throw new Error(`Invalid Sui type: ${tokenType}`);
}
const tokenBridgeStateFields = await getObjectFields(
provider,
tokenBridgeStateObjectId
);
if (!tokenBridgeStateFields) {
throw new Error(
`Unable to fetch object fields from token bridge state. Object ID: ${tokenBridgeStateObjectId}`
);
}
const tokenRegistryObjectId =
tokenBridgeStateFields.token_registry?.fields?.id?.id;
if (!tokenRegistryObjectId) {
throw new Error("Unable to fetch token registry object ID");
}
const tokenRegistryPackageId = getPackageIdFromType(
tokenBridgeStateFields.token_registry?.type
);
if (!tokenRegistryObjectId) {
throw new Error("Unable to fetch token registry package ID");
}
return provider.getDynamicFieldObject({
parentId: tokenRegistryObjectId,
name: {
type: `${tokenRegistryPackageId}::token_registry::Key<${tokenType}>`,
value: {
dummy_field: false,
},
},
});
};
/**
* This function returns the object ID of the `UpgradeCap` that belongs to the
* given package and owner if it exists.
*
* Structs created by the Sui framework such as `UpgradeCap`s all have the same
* type (e.g. `0x2::package::UpgradeCap`) and have a special field, `package`,
* we can use to differentiate them.
* @param provider Sui RPC provider
* @param owner Address of the current owner of the `UpgradeCap`
* @param packageId ID of the package that the `UpgradeCap` was created for
* @returns The object ID of the `UpgradeCap` if it exists, otherwise `null`
*/
export const getUpgradeCapObjectId = async (
provider: JsonRpcProvider,
owner: string,
packageId: string
): Promise<string | null> => {
const res = await provider.getOwnedObjects({
owner,
filter: { StructType: UPGRADE_CAP_TYPE },
options: {
showContent: true,
},
});
if (!res || !res.data) {
throw new SuiRpcValidationError(res);
}
const objects = res.data.filter(
(o) =>
o.data?.objectId &&
o.data?.content?.dataType === "moveObject" &&
o.data?.content?.fields?.package === packageId
);
if (objects.length === 1) {
// We've found the object we're looking for
return objects[0].data?.objectId ?? null;
} else if (objects.length > 1) {
const objectsStr = JSON.stringify(objects, null, 2);
throw new Error(
`Found multiple upgrade capabilities owned by ${owner} from package ${packageId}. Objects: ${objectsStr}`
);
} else {
return null;
}
};
/**
* Get the fully qualified type of a wrapped asset published to the given
* package ID.
*
* All wrapped assets that are registered with the token bridge must satisfy
* the requirement that module name is `coin` (source: https://github.com/wormhole-foundation/wormhole/blob/a1b3773ee42507122c3c4c3494898fbf515d0712/sui/token_bridge/sources/create_wrapped.move#L88).
* As a result, all wrapped assets share the same module name and struct name,
* since the struct name is necessarily `COIN` since it is a OTW.
* @param coinPackageId packageId of the wrapped asset
* @returns Fully qualified type of the wrapped asset
*/
export const getWrappedCoinType = (coinPackageId: string): string => {
if (!isValidSuiAddress(coinPackageId)) {
throw new Error(`Invalid package ID: ${coinPackageId}`);
}
return `${coinPackageId}::coin::COIN`;
};
export const isSameType = (a: string, b: string) => {
try {
return normalizeSuiType(a) === normalizeSuiType(b);
} catch (e) {
return false;
}
};
export const isSuiError = (error: any): error is SuiError => {
return (
error && typeof error === "object" && "code" in error && "message" in error
);
};
/**
* This method validates any Sui address, even if it's not 32 bytes long, i.e.
* "0x2". This differs from Mysten's implementation, which requires that the
* given address is 32 bytes long.
* @param address Address to check
* @returns If given address is a valid Sui address or not
*/
export const isValidSuiAddress = (address: string): boolean =>
isValidFullSuiAddress(normalizeSuiAddress(address));
export const isValidSuiType = (type: string): boolean => {
const tokens = type.split("::");
if (tokens.length !== 3) {
return false;
}
return isValidSuiAddress(tokens[0]) && !!tokens[1] && !!tokens[2];
};
export const normalizeSuiType = (type: string): string => {
const tokens = type.split("::");
if (tokens.length < 3 || !isValidSuiAddress(tokens[0])) {
throw new Error(`Invalid Sui type: ${type}`);
}
return [normalizeSuiAddress(tokens[0]), ...tokens.slice(1)].join("::");
};
/**
* This method removes leading zeroes for types, as we found some getDynamicFieldObject
* value types to be stripped of leading zeroes
*/
export const unnormalizeSuiAddress = (type: string): string =>
type.replace(/^(0x)(0*)/, "0x");

View File

@ -56,7 +56,7 @@ import {
ETH_PRIVATE_KEY7,
TEST_ERC20,
WORMHOLE_RPC_HOSTS,
} from "./consts";
} from "./utils/consts";
const CORE_ID = BigInt(4);
const TOKEN_BRIDGE_ID = BigInt(6);

View File

@ -53,7 +53,7 @@ import {
ETH_PRIVATE_KEY6,
TEST_ERC20,
WORMHOLE_RPC_HOSTS,
} from "./consts";
} from "./utils/consts";
const JEST_TEST_TIMEOUT = 60000;
jest.setTimeout(JEST_TEST_TIMEOUT);

View File

@ -41,7 +41,7 @@ import {
SOLANA_PRIVATE_KEY,
TEST_ERC20,
WORMHOLE_RPC_HOSTS,
} from "./consts";
} from "./utils/consts";
jest.setTimeout(60000);

View File

@ -26,8 +26,8 @@ import {
ETH_PRIVATE_KEY5,
NEAR_NODE_URL,
TEST_ERC20,
} from "./consts";
import { getSignedVAABySequence } from "./helpers";
} from "./utils/consts";
import { getSignedVAABySequence } from "./utils/helpers";
import { Account, connect, KeyPair, keyStores, Near } from "near-api-js";
import {
FinalExecutionOutcome,

View File

@ -41,7 +41,7 @@ import {
SOLANA_PRIVATE_KEY,
TEST_SOLANA_TOKEN,
WORMHOLE_RPC_HOSTS,
} from "./consts";
} from "./utils/consts";
jest.setTimeout(60000);

View File

@ -0,0 +1,633 @@
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import {
afterAll,
beforeAll,
describe,
expect,
jest,
test,
} from "@jest/globals";
import {
Connection,
Ed25519Keypair,
JsonRpcProvider,
RawSigner,
fromB64,
getMoveObjectType,
getPublishedObjectChanges,
} from "@mysten/sui.js";
import { ethers } from "ethers";
import { parseUnits } from "ethers/lib/utils";
import {
approveEth,
attestFromEth,
attestFromSui,
createWrappedOnSui,
createWrappedOnSuiPrepare,
getEmitterAddressEth,
getForeignAssetSui,
getIsTransferCompletedEth,
getIsTransferCompletedSui,
getIsWrappedAssetSui,
getOriginalAssetSui,
getSignedVAAWithRetry,
parseAttestMetaVaa,
parseSequenceFromLogEth,
redeemOnEth,
redeemOnSui,
transferFromEth,
transferFromSui,
updateWrappedOnSui,
} from "../..";
import { MockTokenBridge } from "../../mock/tokenBridge";
import { MockGuardians } from "../../mock/wormhole";
import {
executeTransactionBlock,
getEmitterAddressAndSequenceFromResponseSui,
getInnerType,
getPackageId,
getWrappedCoinType,
} from "../../sui";
import {
CHAIN_ID_ETH,
CHAIN_ID_SUI,
CONTRACTS,
hexToUint8Array,
tryNativeToHexString,
tryNativeToUint8Array,
} from "../../utils";
import { Payload, VAA, parse, serialiseVAA } from "../../vaa/generic";
import {
ETH_NODE_URL,
ETH_PRIVATE_KEY10,
SUI_FAUCET_URL,
SUI_NODE_URL,
TEST_ERC20,
WORMHOLE_RPC_HOSTS,
} from "./utils/consts";
import {
assertIsNotNull,
assertIsNotNullOrUndefined,
mintAndTransferCoinSui,
} from "./utils/helpers";
jest.setTimeout(60000);
// Sui constants
const SUI_CORE_BRIDGE_STATE_OBJECT_ID = CONTRACTS.DEVNET.sui.core;
const SUI_TOKEN_BRIDGE_STATE_OBJECT_ID = CONTRACTS.DEVNET.sui.token_bridge;
const SUI_DEPLOYER_PRIVATE_KEY = "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb";
const suiKeypair: Ed25519Keypair = Ed25519Keypair.fromSecretKey(
fromB64(SUI_DEPLOYER_PRIVATE_KEY).slice(1)
);
const suiAddress: string = suiKeypair.getPublicKey().toSuiAddress();
const suiProvider: JsonRpcProvider = new JsonRpcProvider(
new Connection({
fullnode: SUI_NODE_URL,
faucet: SUI_FAUCET_URL,
})
);
const suiSigner: RawSigner = new RawSigner(suiKeypair, suiProvider);
// Eth constants
const ETH_CORE_BRIDGE_ADDRESS = CONTRACTS.DEVNET.ethereum.core;
const ETH_TOKEN_BRIDGE_ADDRESS = CONTRACTS.DEVNET.ethereum.token_bridge;
const ethProvider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
const ethSigner = new ethers.Wallet(ETH_PRIVATE_KEY10, ethProvider);
let suiCoreBridgePackageId: string;
let suiTokenBridgePackageId: string;
beforeAll(async () => {
suiCoreBridgePackageId = await getPackageId(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID
);
suiTokenBridgePackageId = await getPackageId(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID
);
});
afterAll(async () => {
await ethProvider.destroy();
});
// Modify the VAA to only have 1 guardian signature
// TODO: remove this when we can deploy the devnet core contract
// deterministically with multiple guardians in the initial guardian set
// Currently the core contract is setup with only 1 guardian in the set
function sliceVAASignatures(vaa: Uint8Array) {
const parsedVAA = parse(Buffer.from([...vaa]));
parsedVAA.guardianSetIndex = 0;
parsedVAA.signatures = [parsedVAA.signatures[0]];
return hexToUint8Array(serialiseVAA(parsedVAA as VAA<Payload>));
}
describe("Sui SDK tests", () => {
test("Test prebuilt coin build output", async () => {
// const vaa =
// "0100000000010026ff86c07ef853ef955a63c58a8d08eeb2ac232b91e725bd41baeb3c05c5c18d07aef3c02dc3d5ca8ad0600a447c3d55386d0a0e85b23378d438fbb1e207c3b600000002c3a86f000000020000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16000000000000000001020000000000000000000000002d8be6bf0baa74e0a907016679cae9190e80dd0a000212544b4e0000000000000000000000000000000000000000000000000000000000457468657265756d205465737420546f6b656e00000000000000000000000000";
// const build = getCoinBuildOutput(
// suiCoreBridgePackageId,
// suiTokenBridgePackageId,
// vaa
// );
// const buildManual = await getCoinBuildOutputManual(
// "DEVNET",
// suiCoreBridgePackageId,
// suiTokenBridgePackageId,
// vaa
// );
// expect(build).toMatchObject(buildManual);
// expect(buildManual).toMatchObject(build);
});
test("Transfer native ERC-20 from Ethereum to Sui and back", async () => {
// Attest on Ethereum
const ethAttestTxRes = await attestFromEth(
ETH_TOKEN_BRIDGE_ADDRESS,
ethSigner,
TEST_ERC20
);
// Get attest VAA
const attestSequence = parseSequenceFromLogEth(
ethAttestTxRes,
ETH_CORE_BRIDGE_ADDRESS
);
expect(attestSequence).toBeTruthy();
let { vaaBytes: attestVAA }: { vaaBytes: Uint8Array } =
await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
CHAIN_ID_ETH,
getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS),
attestSequence,
{
transport: NodeHttpTransport(),
},
1000,
5
);
const slicedAttestVAA = sliceVAASignatures(attestVAA);
console.log(Buffer.from(slicedAttestVAA).toString("hex"));
expect(slicedAttestVAA).toBeTruthy();
// Start create wrapped on Sui
const suiPrepareRegistrationTxPayload = await createWrappedOnSuiPrepare(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
parseAttestMetaVaa(slicedAttestVAA).decimals,
suiAddress
);
const suiPrepareRegistrationTxRes = await executeTransactionBlock(
suiSigner,
suiPrepareRegistrationTxPayload
);
suiPrepareRegistrationTxRes.effects?.status.status === "failure" &&
console.log(JSON.stringify(suiPrepareRegistrationTxRes.effects, null, 2));
expect(suiPrepareRegistrationTxRes.effects?.status.status).toBe("success");
// Complete create wrapped on Sui
const wrappedAssetSetupEvent =
suiPrepareRegistrationTxRes.objectChanges?.find(
(oc) =>
oc.type === "created" && oc.objectType.includes("WrappedAssetSetup")
);
const wrappedAssetSetupType =
(wrappedAssetSetupEvent?.type === "created" &&
wrappedAssetSetupEvent.objectType) ||
undefined;
assertIsNotNullOrUndefined(wrappedAssetSetupType);
const publishEvents = getPublishedObjectChanges(
suiPrepareRegistrationTxRes
);
expect(publishEvents.length).toBe(1);
const coinPackageId = publishEvents[0].packageId;
const suiCompleteRegistrationTxPayload = await createWrappedOnSui(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
suiAddress,
coinPackageId,
wrappedAssetSetupType,
slicedAttestVAA
);
const suiCompleteRegistrationTxRes = await executeTransactionBlock(
suiSigner,
suiCompleteRegistrationTxPayload
);
suiCompleteRegistrationTxRes.effects?.status.status === "failure" &&
console.log(
JSON.stringify(suiCompleteRegistrationTxRes.effects, null, 2)
);
expect(suiCompleteRegistrationTxRes.effects?.status.status).toBe("success");
// Generate new VAA
const {
emitterAddress: ethEmitter,
emitterChain,
tokenAddress,
decimals,
symbol,
} = parseAttestMetaVaa(slicedAttestVAA);
const mockTokenBridge = new MockTokenBridge(
ethEmitter.toString("hex"),
emitterChain,
1
);
const updatedAttestPayload = mockTokenBridge.publishAttestMeta(
tokenAddress.toString("hex"),
decimals,
symbol,
"HELLO"
);
const mockGuardians = new MockGuardians(0, [
"cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0",
]);
const updatedAttestVAA = new Uint8Array(
mockGuardians.addSignatures(updatedAttestPayload, [0])
);
// Update wrapped
const updateWrappedTxPayload = await updateWrappedOnSui(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
coinPackageId,
updatedAttestVAA
);
const updateWrappedTxRes = await executeTransactionBlock(
suiSigner,
updateWrappedTxPayload
);
updateWrappedTxRes.effects?.status.status === "failure" &&
console.log(JSON.stringify(updateWrappedTxRes.effects, null, 2));
expect(updateWrappedTxRes.effects?.status.status).toBe("success");
// Check if update was propogated to coin metadata
const newCoinMetadata = await suiProvider.getCoinMetadata({
coinType: getWrappedCoinType(coinPackageId),
});
expect(newCoinMetadata?.name).toContain("HELLO");
// Get foreign asset
const originAssetHex = tryNativeToHexString(TEST_ERC20, CHAIN_ID_ETH);
if (!originAssetHex) {
throw new Error("originAssetHex is null");
}
const foreignAsset = await getForeignAssetSui(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
CHAIN_ID_ETH,
hexToUint8Array(originAssetHex)
);
assertIsNotNull(foreignAsset);
expect(
await getIsWrappedAssetSui(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
foreignAsset
)
).toBe(true);
const originalAsset = await getOriginalAssetSui(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
foreignAsset
);
expect(originalAsset).toMatchObject({
isWrapped: true,
chainId: CHAIN_ID_ETH,
assetAddress: hexToUint8Array(originAssetHex),
});
const transferAmount = parseUnits("1", 18);
const returnAmount = parseUnits("1", 8);
// Transfer to Sui
await approveEth(
CONTRACTS.DEVNET.ethereum.token_bridge,
TEST_ERC20,
ethSigner,
transferAmount
);
const transferReceipt = await transferFromEth(
CONTRACTS.DEVNET.ethereum.token_bridge,
ethSigner,
TEST_ERC20,
transferAmount,
CHAIN_ID_SUI,
tryNativeToUint8Array(suiAddress, CHAIN_ID_SUI)
);
const ethSequence = parseSequenceFromLogEth(
transferReceipt,
ETH_CORE_BRIDGE_ADDRESS
);
let { vaaBytes: transferFromEthVAA } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
CHAIN_ID_ETH,
getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS),
ethSequence,
{
transport: NodeHttpTransport(),
},
1000,
5
);
const slicedTransferFromEthVAA = sliceVAASignatures(transferFromEthVAA);
expect(slicedTransferFromEthVAA).toBeTruthy();
// Redeem on Sui
const redeemPayload = await redeemOnSui(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
slicedTransferFromEthVAA
);
const suiRedeemTxResult = await executeTransactionBlock(
suiSigner,
redeemPayload
);
suiRedeemTxResult.effects?.status.status === "failure" &&
console.error(suiRedeemTxResult.effects?.status.error);
expect(suiRedeemTxResult.effects?.status.status).toBe("success");
expect(
await getIsTransferCompletedSui(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
slicedTransferFromEthVAA
)
).toBe(true);
// Transfer back to Eth
const coinType = await getForeignAssetSui(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
CHAIN_ID_ETH,
originalAsset.assetAddress
);
assertIsNotNull(coinType);
const coins = (
await suiProvider.getCoins({
owner: suiAddress,
coinType: coinType,
})
).data;
console.log({ coins, coinType });
const suiTransferTxPayload = await transferFromSui(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
coins,
coinType,
returnAmount.toBigInt(),
CHAIN_ID_ETH,
tryNativeToUint8Array(ethSigner.address, CHAIN_ID_ETH)
);
const suiTransferTxResult = await executeTransactionBlock(
suiSigner,
suiTransferTxPayload
);
suiTransferTxResult.effects?.status.status === "failure" &&
console.error(suiTransferTxResult.effects?.status.error);
expect(suiTransferTxResult.effects?.status.status).toBe("success");
const { sequence, emitterAddress } =
getEmitterAddressAndSequenceFromResponseSui(
suiCoreBridgePackageId,
suiTransferTxResult
);
// Fetch the transfer VAA
const { vaaBytes: transferFromSuiVAA } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
CHAIN_ID_SUI,
emitterAddress,
sequence,
{
transport: NodeHttpTransport(),
},
1000,
5
);
expect(transferFromSuiVAA).toBeTruthy();
// Redeem on Ethereum
await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, ethSigner, transferFromSuiVAA);
expect(
await getIsTransferCompletedEth(
ETH_TOKEN_BRIDGE_ADDRESS,
ethProvider,
transferFromSuiVAA
)
).toBe(true);
});
test.only("Transfer non-SUI Sui token to Ethereum and back", async () => {
// Get COIN_8 coin type
const res = await suiProvider.getOwnedObjects({
owner: suiAddress,
options: { showContent: true, showType: true },
});
const coins = res.data.filter((o) => {
const type = o.data?.type ?? "";
return type.includes("TreasuryCap") && type.includes("COIN_8");
});
expect(coins.length).toBeGreaterThan(0);
const coin8 = coins[0];
const coin8Type = getInnerType(getMoveObjectType(coin8) ?? "");
const coin8TreasuryCapObjectId = coin8.data?.objectId;
assertIsNotNullOrUndefined(coin8Type);
assertIsNotNullOrUndefined(coin8TreasuryCapObjectId);
expect(
await getIsWrappedAssetSui(
suiProvider,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
coin8Type
)
).toBe(false);
// Mint coins
const transferAmount = parseUnits("1", 8).toBigInt();
const suiMintTxPayload = mintAndTransferCoinSui(
coin8TreasuryCapObjectId,
coin8Type,
transferAmount,
suiAddress
);
let result = await executeTransactionBlock(suiSigner, suiMintTxPayload);
result.effects?.status.status === "failure" &&
console.log(JSON.stringify(result.effects, null, 2));
expect(result.effects?.status.status).toBe("success");
// Attest on Sui
const suiAttestTxPayload = await attestFromSui(
suiProvider,
SUI_CORE_BRIDGE_STATE_OBJECT_ID,
SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
coin8Type
);
result = await executeTransactionBlock(suiSigner, suiAttestTxPayload);
result.effects?.status.status === "failure" &&
console.log(JSON.stringify(result.effects, null, 2));
expect(result.effects?.status.status).toBe("success");
const { sequence: attestSequence, emitterAddress: attestEmitterAddress } =
getEmitterAddressAndSequenceFromResponseSui(
suiCoreBridgePackageId,
result
);
expect(attestSequence).toBeTruthy();
expect(attestEmitterAddress).toBeTruthy();
const { vaaBytes: attestVAA } = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
CHAIN_ID_SUI,
attestEmitterAddress,
attestSequence,
{
transport: NodeHttpTransport(),
},
1000,
30
);
console.log(parseAttestMetaVaa(attestVAA));
expect(attestVAA).toBeTruthy();
// // Create wrapped on Ethereum
// try {
// await createWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, ethSigner, attestVAA);
// } catch (e) {
// // this could fail because the token is already attested (in an unclean env)
// }
// const { tokenAddress } = parseAttestMetaVaa(attestVAA);
// expect(
// await getOriginalAssetSui(
// suiProvider,
// SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
// coin8Type
// )
// ).toMatchObject({
// isWrapped: false,
// chainId: CHAIN_ID_SUI,
// assetAddress: new Uint8Array(tokenAddress),
// });
// const coin8Coins = await suiProvider.getCoins({
// owner: suiAddress,
// coinType: coin8Type,
// });
// expect(coin8Coins.data.length).toBeGreaterThan(0);
// // Transfer to Ethereum
// const suiTransferTxPayload = await transferFromSui(
// suiProvider,
// SUI_CORE_BRIDGE_STATE_OBJECT_ID,
// SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
// coin8Coins.data,
// coin8Type,
// transferAmount,
// CHAIN_ID_ETH,
// tryNativeToUint8Array(ethSigner.address, CHAIN_ID_ETH)
// );
// result = await executeTransactionBlock(suiSigner, suiTransferTxPayload);
// result.effects?.status.status === "failure" &&
// console.log(JSON.stringify(result.effects, null, 2));
// expect(result.effects?.status.status).toBe("success");
// const { sequence, emitterAddress } =
// getEmitterAddressAndSequenceFromResponseSui(
// suiCoreBridgePackageId,
// result
// );
// expect(sequence).toBeTruthy();
// expect(emitterAddress).toBeTruthy();
// // Fetch the transfer VAA
// const { vaaBytes: transferVAA } = await getSignedVAAWithRetry(
// WORMHOLE_RPC_HOSTS,
// CHAIN_ID_SUI,
// emitterAddress,
// sequence!,
// {
// transport: NodeHttpTransport(),
// },
// 1000,
// 30
// );
// // Redeem on Ethereum
// await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, ethSigner, transferVAA);
// expect(
// await getIsTransferCompletedEth(
// ETH_TOKEN_BRIDGE_ADDRESS,
// ethProvider,
// transferVAA
// )
// ).toBe(true);
// // Transfer back to Sui
// const ethTokenAddress = await getForeignAssetEth(
// ETH_TOKEN_BRIDGE_ADDRESS,
// ethProvider,
// CHAIN_ID_SUI,
// tokenAddress
// );
// expect(ethTokenAddress).toBeTruthy();
// await approveEth(
// ETH_TOKEN_BRIDGE_ADDRESS,
// ethTokenAddress!,
// ethSigner,
// transferAmount
// );
// const transferReceipt = await transferFromEth(
// ETH_TOKEN_BRIDGE_ADDRESS,
// ethSigner,
// ethTokenAddress!,
// transferAmount,
// CHAIN_ID_SUI,
// tryNativeToUint8Array(suiAddress, CHAIN_ID_SUI)
// );
// const ethSequence = parseSequenceFromLogEth(
// transferReceipt,
// ETH_CORE_BRIDGE_ADDRESS
// );
// expect(ethSequence).toBeTruthy();
// const { vaaBytes: ethTransferVAA } = await getSignedVAAWithRetry(
// WORMHOLE_RPC_HOSTS,
// CHAIN_ID_ETH,
// getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS),
// ethSequence,
// {
// transport: NodeHttpTransport(),
// },
// 1000,
// 30
// );
// const slicedVAA = sliceVAASignatures(ethTransferVAA);
// // Redeem on Sui
// expect(
// await getIsTransferCompletedSui(
// suiProvider,
// SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
// slicedVAA
// )
// ).toBe(false);
// const redeemPayload = await redeemOnSui(
// suiProvider,
// SUI_CORE_BRIDGE_STATE_OBJECT_ID,
// SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
// slicedVAA
// );
// result = await executeTransactionBlock(suiSigner, redeemPayload);
// result.effects?.status.status === "failure" &&
// console.log(JSON.stringify(result.effects, null, 2));
// expect(result.effects?.status.status).toBe("success");
// expect(
// await getIsTransferCompletedSui(
// suiProvider,
// SUI_TOKEN_BRIDGE_STATE_OBJECT_ID,
// slicedVAA
// )
// ).toBe(true);
});
});

View File

@ -45,13 +45,13 @@ import {
TERRA_PUBLIC_KEY,
TEST_ERC20,
WORMHOLE_RPC_HOSTS,
} from "./consts";
} from "./utils/consts";
import {
getSignedVAABySequence,
getTerraGasPrices,
queryBalanceOnTerra,
waitForTerraExecution,
} from "./helpers";
} from "./utils/helpers";
jest.setTimeout(60000);

View File

@ -40,8 +40,8 @@ import {
TERRA_NODE_URL,
TERRA_PRIVATE_KEY2,
TEST_ERC20,
} from "./consts";
import { getSignedVAABySequence, waitForTerraExecution } from "./helpers";
} from "./utils/consts";
import { getSignedVAABySequence, waitForTerraExecution } from "./utils/helpers";
const lcd = new LCDClient({
URL: TERRA2_NODE_URL,

View File

@ -22,6 +22,8 @@ export const ETH_PRIVATE_KEY7 =
"0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3"; // account 7 - algorand tests
export const ETH_PRIVATE_KEY9 =
"0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773"; // account 9 - accountant tests
export const ETH_PRIVATE_KEY10 =
"0x77c5495fbb039eed474fc940f29955ed0531693cc9212911efd35dff0373153f"; // account 10 - sui tests
export const SOLANA_HOST = ci
? "http://solana-devnet:8899"
: "http://localhost:8899";
@ -95,12 +97,7 @@ export const APTOS_FAUCET_URL = ci
export const APTOS_PRIVATE_KEY =
"537c1f91e56891445b491068f519b705f8c0f1a1e66111816dd5d4aa85b8113d";
describe("consts should exist", () => {
it("has Solana test token", () => {
expect.assertions(1);
const connection = new Connection(SOLANA_HOST, "confirmed");
return expect(
connection.getAccountInfo(new PublicKey(TEST_SOLANA_TOKEN))
).resolves.toBeTruthy();
});
});
export const SUI_NODE_URL = ci ? "http://sui:9000" : "http://localhost:9000";
export const SUI_FAUCET_URL = ci
? "http://sui:5003/gas"
: "http://localhost:5003/gas";

View File

@ -1,7 +1,9 @@
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import { expect } from "@jest/globals";
import { TransactionBlock } from "@mysten/sui.js";
import { LCDClient, MnemonicKey, TxInfo } from "@terra-money/terra.js";
import axios from "axios";
import { ChainId, getSignedVAAWithRetry } from "../..";
import { ChainId, getSignedVAAWithRetry } from "../../..";
import {
TERRA_CHAIN_ID,
TERRA_GAS_PRICES_URL,
@ -98,3 +100,30 @@ export async function queryBalanceOnTerra(asset: string): Promise<number> {
export async function getTerraGasPrices() {
return axios.get(TERRA_GAS_PRICES_URL).then((result) => result.data);
}
// https://github.com/microsoft/TypeScript/issues/34523
export const assertIsNotNull: <T>(x: T | null) => asserts x is T = (x) => {
expect(x).not.toBeNull();
};
export const assertIsNotNullOrUndefined: <T>(
x: T | null | undefined
) => asserts x is T = (x) => {
expect(x).not.toBeNull();
expect(x).not.toBeUndefined();
};
export function mintAndTransferCoinSui(
treasuryCap: string,
coinType: string,
amount: bigint,
recipient: string
) {
const tx = new TransactionBlock();
tx.moveCall({
target: "0x2::coin::mint_and_transfer",
arguments: [tx.object(treasuryCap), tx.pure(amount), tx.pure(recipient)],
typeArguments: [coinType],
});
return tx;
}

View File

@ -1,3 +1,8 @@
import {
JsonRpcProvider,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import {
Commitment,
Connection,
@ -10,30 +15,31 @@ import { MsgExecuteContract } from "@terra-money/terra.js";
import { MsgExecuteContract as XplaMsgExecuteContract } from "@xpla/xpla.js";
import {
Algodv2,
OnApplicationComplete,
SuggestedParams,
bigIntToBytes,
decodeAddress,
getApplicationAddress,
makeApplicationCallTxnFromObject,
makePaymentTxnWithSuggestedParamsFromObject,
OnApplicationComplete,
SuggestedParams,
} from "algosdk";
import { Types } from "aptos";
import BN from "bn.js";
import { PayableOverrides, ethers } from "ethers";
import { ethers, PayableOverrides } from "ethers";
import { FunctionCallOptions } from "near-api-js/lib/account";
import { Provider } from "near-api-js/lib/providers";
import { getIsWrappedAssetNear } from ".";
import { TransactionSignerPair, getMessageFee, optin } from "../algorand";
import { getMessageFee, optin, TransactionSignerPair } from "../algorand";
import { attestToken as attestTokenAptos } from "../aptos";
import { isNativeDenomXpla } from "../cosmwasm";
import { Bridge__factory } from "../ethers-contracts";
import { createBridgeFeeTransferInstruction } from "../solana";
import { createAttestTokenInstruction } from "../solana/tokenBridge";
import { getPackageId } from "../sui/utils";
import { isNativeDenom } from "../terra";
import {
ChainId,
callFunctionNear,
ChainId,
hashAccount,
textToHexString,
textToUint8Array,
@ -306,3 +312,45 @@ export function attestFromAptos(
): Types.EntryFunctionPayload {
return attestTokenAptos(tokenBridgeAddress, tokenChain, tokenAddress);
}
export async function attestFromSui(
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
coinType: string,
feeAmount: BigInt = BigInt(0)
): Promise<TransactionBlock> {
const metadata = await provider.getCoinMetadata({ coinType });
if (metadata === null || metadata.id === null) {
throw new Error(`Coin metadata ID for type ${coinType} not found`);
}
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
const tx = new TransactionBlock();
const [feeCoin] = tx.splitCoins(tx.gas, [tx.pure(feeAmount)]);
const [messageTicket] = tx.moveCall({
target: `${tokenBridgePackageId}::attest_token::attest_token`,
arguments: [
tx.object(tokenBridgeStateObjectId),
tx.object(metadata.id),
tx.pure(createNonce().readUInt32LE()),
],
typeArguments: [coinType],
});
tx.moveCall({
target: `${coreBridgePackageId}::publish_message::publish_message`,
arguments: [
tx.object(coreBridgeStateObjectId),
feeCoin,
messageTicket,
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}

View File

@ -1,3 +1,8 @@
import {
JsonRpcProvider,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import {
Commitment,
Connection,
@ -10,7 +15,7 @@ import { MsgExecuteContract as XplaMsgExecuteContract } from "@xpla/xpla.js";
import { Algodv2 } from "algosdk";
import { Types } from "aptos";
import BN from "bn.js";
import { Overrides, ethers } from "ethers";
import { ethers, Overrides } from "ethers";
import { fromUint8Array } from "js-base64";
import { FunctionCallOptions } from "near-api-js/lib/account";
import { Provider } from "near-api-js/lib/providers";
@ -21,6 +26,13 @@ import {
} from "../aptos";
import { Bridge__factory } from "../ethers-contracts";
import { createCreateWrappedInstruction } from "../solana/tokenBridge";
import {
getOwnedObjectId,
getPackageId,
getUpgradeCapObjectId,
getWrappedCoinType,
publishCoin,
} from "../sui";
import { callFunctionNear } from "../utils";
import { SignedVaa } from "../vaa";
@ -157,3 +169,104 @@ export function createWrappedOnAptos(
): Types.EntryFunctionPayload {
return createWrappedCoinAptos(tokenBridgeAddress, attestVAA);
}
export async function createWrappedOnSuiPrepare(
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
decimals: number,
signerAddress: string
): Promise<TransactionBlock> {
return publishCoin(
provider,
coreBridgeStateObjectId,
tokenBridgeStateObjectId,
decimals,
signerAddress
);
}
export async function createWrappedOnSui(
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
signerAddress: string,
coinPackageId: string,
wrappedAssetSetupType: string,
attestVAA: Uint8Array
): Promise<TransactionBlock> {
// WrappedAssetSetup looks like
// 0x92d81f28c167d90f84638c654b412fe7fa8e55bdfac7f638bdcf70306289be86::create_wrapped::WrappedAssetSetup<0xa40e0511f7d6531dd2dfac0512c7fd4a874b76f5994985fb17ee04501a2bb050::coin::COIN, 0x4eb7c5bca3759ab3064b46044edb5668c9066be8a543b28b58375f041f876a80::version_control::V__0_1_1>
// ugh
const versionType = wrappedAssetSetupType.split(", ")[1].replace(">", "");
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
// Get coin metadata
const coinType = getWrappedCoinType(coinPackageId);
const coinMetadataObjectId = (await provider.getCoinMetadata({ coinType }))
?.id;
if (!coinMetadataObjectId) {
throw new Error(
`Coin metadata object not found for coin type ${coinType}.`
);
}
const wrappedAssetSetupObjectId = await getOwnedObjectId(
provider,
signerAddress,
wrappedAssetSetupType
);
if (!wrappedAssetSetupObjectId) {
throw new Error(`WrappedAssetSetup not found`);
}
// Get coin upgrade capability
const coinUpgradeCapObjectId = await getUpgradeCapObjectId(
provider,
signerAddress,
coinPackageId
);
if (!coinUpgradeCapObjectId) {
throw new Error(
`Coin upgrade cap not found for ${coinType} under owner ${signerAddress}. You must call 'createWrappedOnSuiPrepare' first.`
);
}
// Get TokenBridgeMessage
const tx = new TransactionBlock();
const [vaa] = tx.moveCall({
target: `${coreBridgePackageId}::vaa::parse_and_verify`,
arguments: [
tx.object(coreBridgeStateObjectId),
tx.pure([...attestVAA]),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
const [message] = tx.moveCall({
target: `${tokenBridgePackageId}::vaa::verify_only_once`,
arguments: [tx.object(tokenBridgeStateObjectId), vaa],
});
// Construct complete registration payload
tx.moveCall({
target: `${tokenBridgePackageId}::create_wrapped::complete_registration`,
arguments: [
tx.object(tokenBridgeStateObjectId),
tx.object(coinMetadataObjectId),
tx.object(wrappedAssetSetupObjectId),
tx.object(coinUpgradeCapObjectId),
message,
],
typeArguments: [coinType, versionType],
});
return tx;
}

View File

@ -1,3 +1,4 @@
import { JsonRpcProvider } from "@mysten/sui.js";
import { Commitment, Connection, PublicKeyInitData } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
@ -13,11 +14,12 @@ import {
} from "../algorand";
import { Bridge__factory } from "../ethers-contracts";
import { deriveWrappedMintKey, getWrappedMeta } from "../solana/tokenBridge";
import { getTokenCoinType } from "../sui";
import {
CHAIN_ID_ALGORAND,
callFunctionNear,
ChainId,
ChainName,
callFunctionNear,
CHAIN_ID_ALGORAND,
coalesceChainId,
coalesceModuleAddress,
getAssetFullyQualifiedType,
@ -202,3 +204,18 @@ export async function getForeignAssetAptos(
return null;
}
}
export async function getForeignAssetSui(
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string,
originChain: ChainId | ChainName,
originAddress: Uint8Array
): Promise<string | null> {
const originChainId = coalesceChainId(originChain);
return getTokenCoinType(
provider,
tokenBridgeStateObjectId,
originAddress,
originChainId
);
}

View File

@ -1,3 +1,4 @@
import { JsonRpcProvider } from "@mysten/sui.js";
import { Commitment, Connection, PublicKeyInitData } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { LCDClient as XplaLCDClient } from "@xpla/xpla.js";
@ -8,20 +9,21 @@ import { ethers } from "ethers";
import { fromUint8Array } from "js-base64";
import { Provider } from "near-api-js/lib/providers";
import { redeemOnTerra } from ".";
import { TERRA_REDEEMED_CHECK_WALLET_ADDRESS, ensureHexPrefix } from "..";
import { ensureHexPrefix, TERRA_REDEEMED_CHECK_WALLET_ADDRESS } from "..";
import {
BITS_PER_KEY,
calcLogicSigAccount,
MAX_BITS,
_parseVAAAlgorand,
calcLogicSigAccount,
} from "../algorand";
import { TokenBridgeState } from "../aptos/types";
import { getSignedVAAHash } from "../bridge";
import { Bridge__factory } from "../ethers-contracts";
import { getClaim } from "../solana/wormhole";
import { getObjectFields, getTableKeyType } from "../sui/utils";
import { safeBigIntToNumber } from "../utils/bigint";
import { callFunctionNear } from "../utils/near";
import { SignedVaa, parseVaa } from "../vaa/wormhole";
import { parseVaa, SignedVaa } from "../vaa/wormhole";
export async function getIsTransferCompletedEth(
tokenBridgeAddress: string,
@ -265,3 +267,45 @@ export async function getIsTransferCompletedAptos(
return false;
}
}
export async function getIsTransferCompletedSui(
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string,
transferVAA: Uint8Array
): Promise<boolean> {
const tokenBridgeStateFields = await getObjectFields(
provider,
tokenBridgeStateObjectId
);
if (!tokenBridgeStateFields) {
throw new Error("Unable to fetch object fields from token bridge state");
}
const hashes = tokenBridgeStateFields.consumed_vaas?.fields?.hashes;
const tableObjectId = hashes?.fields?.items?.fields?.id?.id;
if (!tableObjectId) {
throw new Error("Unable to fetch consumed VAAs table");
}
const keyType = getTableKeyType(hashes?.fields?.items?.type);
if (!keyType) {
throw new Error("Unable to get key type");
}
const hash = getSignedVAAHash(transferVAA);
const response = await provider.getDynamicFieldObject({
parentId: tableObjectId,
name: {
type: keyType,
value: {
data: [...Buffer.from(hash.slice(2), "hex")],
},
},
});
if (!response.error) {
return true;
}
if (response.error.code === "dynamicFieldNotFound") {
return false;
}
throw new Error(
`Unexpected getDynamicFieldObject response ${response.error}`
);
}

View File

@ -1,3 +1,4 @@
import { JsonRpcProvider } from "@mysten/sui.js";
import { Commitment, Connection, PublicKeyInitData } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { Algodv2, getApplicationAddress } from "algosdk";
@ -5,6 +6,7 @@ import { AptosClient } from "aptos";
import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { getWrappedMeta } from "../solana/tokenBridge";
import { getTokenFromTokenRegistry } from "../sui";
import { coalesceModuleAddress, ensureHexPrefix } from "../utils";
import { safeBigIntToNumber } from "../utils/bigint";
@ -112,3 +114,29 @@ export async function getIsWrappedAssetAptos(
return false;
}
}
export async function getIsWrappedAssetSui(
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string,
type: string
): Promise<boolean> {
// // An easy way to determine if given asset isn't a wrapped asset is to ensure
// // module name and struct name are coin and COIN respectively.
// if (!type.endsWith("::coin::COIN")) {
// return false;
// }
const response = await getTokenFromTokenRegistry(
provider,
tokenBridgeStateObjectId,
type
);
if (!response.error) {
return response.data?.type?.includes("WrappedAsset") || false;
}
if (response.error.code === "dynamicFieldNotFound") {
return false;
}
throw new Error(
`Unexpected getDynamicFieldObject response ${response.error}`
);
}

View File

@ -1,3 +1,4 @@
import { JsonRpcProvider } from "@mysten/sui.js";
import {
Commitment,
Connection,
@ -18,21 +19,28 @@ import { canonicalAddress } from "../cosmos";
import { buildTokenId, isNativeCosmWasmDenom } from "../cosmwasm/address";
import { TokenImplementation__factory } from "../ethers-contracts";
import { getWrappedMeta } from "../solana/tokenBridge";
import {
getFieldsFromObjectResponse,
getTokenFromTokenRegistry,
isValidSuiType,
unnormalizeSuiAddress,
} from "../sui";
import { buildNativeId } from "../terra";
import {
assertChain,
callFunctionNear,
ChainId,
ChainName,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_SUI,
CHAIN_ID_TERRA,
ChainId,
ChainName,
CosmWasmChainId,
CosmWasmChainName,
assertChain,
callFunctionNear,
coalesceChainId,
coalesceCosmWasmChainId,
CosmWasmChainId,
CosmWasmChainName,
hexToUint8Array,
isValidAptosType,
} from "../utils";
@ -267,13 +275,13 @@ export async function getOriginalAssetNear(
/**
* Gets the origin chain ID and address of an asset on Aptos, given its fully qualified type.
* @param client Client used to transfer data to/from Aptos node
* @param tokenBridgeAddress Address of token bridge
* @param tokenBridgePackageId Address of token bridge
* @param fullyQualifiedType Fully qualified type of asset
* @returns Original chain ID and address of asset
*/
export async function getOriginalAssetAptos(
client: AptosClient,
tokenBridgeAddress: string,
tokenBridgePackageId: string,
fullyQualifiedType: string
): Promise<WormholeWrappedInfo> {
if (!isValidAptosType(fullyQualifiedType)) {
@ -285,7 +293,7 @@ export async function getOriginalAssetAptos(
originInfo = (
await client.getAccountResource(
fullyQualifiedType.split("::")[0],
`${tokenBridgeAddress}::state::OriginInfo`
`${tokenBridgePackageId}::state::OriginInfo`
)
).data as OriginInfo;
} catch {
@ -317,3 +325,61 @@ export async function getOriginalAssetAptos(
};
}
}
export async function getOriginalAssetSui(
provider: JsonRpcProvider,
tokenBridgeStateObjectId: string,
coinType: string
): Promise<WormholeWrappedInfo> {
if (!isValidSuiType(coinType)) {
throw new Error(`Invalid Sui type: ${coinType}`);
}
const res = await getTokenFromTokenRegistry(
provider,
tokenBridgeStateObjectId,
coinType
);
const fields = getFieldsFromObjectResponse(res);
if (!fields) {
throw new Error(
`Token of type ${coinType} has not been registered with the token bridge`
);
}
if (
fields.value.type.includes(`wrapped_asset::WrappedAsset<${coinType}>`) ||
fields.value.type.includes(
`wrapped_asset::WrappedAsset<${unnormalizeSuiAddress(coinType)}>`
)
) {
return {
isWrapped: true,
chainId: Number(fields.value.fields.info.fields.token_chain) as ChainId,
assetAddress: new Uint8Array(
fields.value.fields.info.fields.token_address.fields.value.fields.data
),
};
} else if (
fields.value.type.includes(`native_asset::NativeAsset<${coinType}>`) ||
fields.value.type.includes(
`native_asset::NativeAsset<${unnormalizeSuiAddress(coinType)}>`
)
) {
return {
isWrapped: false,
chainId: CHAIN_ID_SUI,
assetAddress: new Uint8Array(
fields.value.fields.token_address.fields.value.fields.data
),
};
}
throw new Error(
`Unrecognized token metadata: ${JSON.stringify(
fields,
null,
2
)}, ${coinType}`
);
}

View File

@ -1,3 +1,8 @@
import {
JsonRpcProvider,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import {
ACCOUNT_SIZE,
createCloseAccountInstruction,
@ -27,9 +32,9 @@ import { fromUint8Array } from "js-base64";
import { FunctionCallOptions } from "near-api-js/lib/account";
import { Provider } from "near-api-js/lib/providers";
import {
TransactionSignerPair,
_parseVAAAlgorand,
_submitVAAAlgorand,
TransactionSignerPair,
} from "../algorand";
import { completeTransferAndRegister } from "../aptos";
import { Bridge__factory } from "../ethers-contracts";
@ -37,11 +42,12 @@ import {
createCompleteTransferNativeInstruction,
createCompleteTransferWrappedInstruction,
} from "../solana/tokenBridge";
import { getPackageId, getTokenCoinType } from "../sui";
import {
callFunctionNear,
ChainId,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
ChainId,
hashLookup,
MAX_VAA_DECIMALS,
uint8ArrayToHex,
@ -356,3 +362,58 @@ export function redeemOnAptos(
): Promise<Types.EntryFunctionPayload> {
return completeTransferAndRegister(client, tokenBridgeAddress, transferVAA);
}
export async function redeemOnSui(
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
transferVAA: Uint8Array
): Promise<TransactionBlock> {
const { tokenAddress, tokenChain } = parseTokenTransferVaa(transferVAA);
const coinType = await getTokenCoinType(
provider,
tokenBridgeStateObjectId,
tokenAddress,
tokenChain
);
if (!coinType) {
throw new Error("Unable to fetch token coinType");
}
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
const tx = new TransactionBlock();
const [verifiedVAA] = tx.moveCall({
target: `${coreBridgePackageId}::vaa::parse_and_verify`,
arguments: [
tx.object(coreBridgeStateObjectId),
tx.pure([...transferVAA]),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
const [tokenBridgeMessage] = tx.moveCall({
target: `${tokenBridgePackageId}::vaa::verify_only_once`,
arguments: [tx.object(tokenBridgeStateObjectId), verifiedVAA],
});
const [relayerReceipt] = tx.moveCall({
target: `${tokenBridgePackageId}::complete_transfer::authorize_transfer`,
arguments: [tx.object(tokenBridgeStateObjectId), tokenBridgeMessage],
typeArguments: [coinType],
});
const [coins] = tx.moveCall({
target: `${tokenBridgePackageId}::complete_transfer::redeem_relayer_payout`,
arguments: [relayerReceipt],
typeArguments: [coinType],
});
tx.moveCall({
target: `${tokenBridgePackageId}::coin_utils::return_nonzero`,
arguments: [coins],
typeArguments: [coinType],
});
return tx;
}

View File

@ -1,3 +1,9 @@
import {
JsonRpcProvider,
SUI_CLOCK_OBJECT_ID,
SUI_TYPE_ARG,
TransactionBlock,
} from "@mysten/sui.js";
import {
ACCOUNT_SIZE,
createCloseAccountInstruction,
@ -12,14 +18,13 @@ import {
Keypair,
PublicKey,
PublicKeyInitData,
Transaction as SolanaTransaction,
SystemProgram,
Transaction as SolanaTransaction,
} from "@solana/web3.js";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { MsgExecuteContract as XplaMsgExecuteContract } from "@xpla/xpla.js";
import {
Algodv2,
Transaction as AlgorandTransaction,
bigIntToBytes,
getApplicationAddress,
makeApplicationCallTxnFromObject,
@ -27,6 +32,7 @@ import {
makePaymentTxnWithSuggestedParamsFromObject,
OnApplicationComplete,
SuggestedParams,
Transaction as AlgorandTransaction,
} from "algosdk";
import { Types } from "aptos";
import BN from "bn.js";
@ -57,12 +63,14 @@ import {
createTransferWrappedInstruction,
createTransferWrappedWithPayloadInstruction,
} from "../solana/tokenBridge";
import { getPackageId, isSameType } from "../sui";
import { SuiCoinObject } from "../sui/types";
import { isNativeDenom } from "../terra";
import {
callFunctionNear,
CHAIN_ID_SOLANA,
ChainId,
ChainName,
CHAIN_ID_SOLANA,
coalesceChainId,
createNonce,
hexToUint8Array,
@ -913,3 +921,90 @@ export function transferFromAptos(
createNonce().readUInt32LE(0)
);
}
export async function transferFromSui(
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
coins: SuiCoinObject[],
coinType: string,
amount: bigint,
recipientChain: ChainId | ChainName,
recipient: Uint8Array,
feeAmount: bigint = BigInt(0),
relayerFee: bigint = BigInt(0),
payload: Uint8Array | null = null
) {
if (payload !== null) {
throw new Error("Sui transfer with payload not implemented");
}
const [primaryCoin, ...mergeCoins] = coins.filter((coin) =>
isSameType(coin.coinType, coinType)
);
if (primaryCoin === undefined) {
throw new Error(
`Coins array doesn't contain any coins of type ${coinType}`
);
}
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
const tx = new TransactionBlock();
const [transferCoin] = (() => {
if (coinType === SUI_TYPE_ARG) {
return tx.splitCoins(tx.gas, [tx.pure(amount)]);
} else {
const primaryCoinInput = tx.object(primaryCoin.coinObjectId);
if (mergeCoins.length) {
tx.mergeCoins(
primaryCoinInput,
mergeCoins.map((coin) => tx.object(coin.coinObjectId))
);
}
return tx.splitCoins(primaryCoinInput, [tx.pure(amount)]);
}
})();
const [feeCoin] = tx.splitCoins(tx.gas, [tx.pure(feeAmount)]);
const [assetInfo] = tx.moveCall({
target: `${tokenBridgePackageId}::state::verified_asset`,
arguments: [tx.object(tokenBridgeStateObjectId)],
typeArguments: [coinType],
});
const [transferTicket, dust] = tx.moveCall({
target: `${tokenBridgePackageId}::transfer_tokens::prepare_transfer`,
arguments: [
assetInfo,
transferCoin,
tx.pure(coalesceChainId(recipientChain)),
tx.pure([...recipient]),
tx.pure(relayerFee),
tx.pure(createNonce().readUInt32LE()),
],
typeArguments: [coinType],
});
tx.moveCall({
target: `${tokenBridgePackageId}::coin_utils::return_nonzero`,
arguments: [dust],
typeArguments: [coinType],
});
const [messageTicket] = tx.moveCall({
target: `${tokenBridgePackageId}::transfer_tokens::transfer_tokens`,
arguments: [tx.object(tokenBridgeStateObjectId), transferTicket],
typeArguments: [coinType],
});
tx.moveCall({
target: `${coreBridgePackageId}::publish_message::publish_message`,
arguments: [
tx.object(coreBridgeStateObjectId),
feeCoin,
messageTicket,
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}

View File

@ -1,3 +1,8 @@
import {
JsonRpcProvider,
SUI_CLOCK_OBJECT_ID,
TransactionBlock,
} from "@mysten/sui.js";
import { ethers, Overrides } from "ethers";
import {
createWrappedOnAlgorand,
@ -8,6 +13,7 @@ import {
createWrappedOnXpla,
} from ".";
import { Bridge__factory } from "../ethers-contracts";
import { getPackageId, getWrappedCoinType } from "../sui";
export async function updateWrappedOnEth(
tokenBridgeAddress: string,
@ -32,3 +38,59 @@ export const updateWrappedOnAlgorand = createWrappedOnAlgorand;
export const updateWrappedOnNear = createWrappedOnNear;
export const updateWrappedOnAptos = createWrappedOnAptos;
export async function updateWrappedOnSui(
provider: JsonRpcProvider,
coreBridgeStateObjectId: string,
tokenBridgeStateObjectId: string,
coinPackageId: string,
attestVAA: Uint8Array
): Promise<TransactionBlock> {
const coreBridgePackageId = await getPackageId(
provider,
coreBridgeStateObjectId
);
const tokenBridgePackageId = await getPackageId(
provider,
tokenBridgeStateObjectId
);
// Get coin metadata
const coinType = getWrappedCoinType(coinPackageId);
const coinMetadataObjectId = (await provider.getCoinMetadata({ coinType }))
?.id;
if (!coinMetadataObjectId) {
throw new Error(
`Coin metadata object not found for coin type ${coinType}.`
);
}
// Get verified VAA
const tx = new TransactionBlock();
const [vaa] = tx.moveCall({
target: `${coreBridgePackageId}::vaa::parse_and_verify`,
arguments: [
tx.object(coreBridgeStateObjectId),
tx.pure([...attestVAA]),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
// Get TokenBridgeMessage
const [message] = tx.moveCall({
target: `${tokenBridgePackageId}::vaa::verify_only_once`,
arguments: [tx.object(tokenBridgeStateObjectId), vaa],
});
// Construct complete registration payload
tx.moveCall({
target: `${tokenBridgePackageId}::create_wrapped::update_attestation`,
arguments: [
tx.object(tokenBridgeStateObjectId),
tx.object(coinMetadataObjectId),
message,
],
typeArguments: [coinType],
});
return tx;
}

View File

@ -35,6 +35,8 @@ import {
} from "./consts";
import { hashLookup } from "./near";
import { getExternalAddressFromType, isValidAptosType } from "./aptos";
import { isValidSuiAddress } from "@mysten/sui.js";
import { isValidSuiType } from "../sui";
/**
*
@ -67,7 +69,7 @@ export const uint8ArrayToHex = (a: Uint8Array): string =>
export const hexToUint8Array = (h: string): Uint8Array => {
if (h.startsWith("0x")) h = h.slice(2);
return new Uint8Array(Buffer.from(h, "hex"));
}
};
/**
*
@ -250,7 +252,12 @@ export const tryNativeToHexString = (
} else if (chainId === CHAIN_ID_OSMOSIS) {
throw Error("hexToNativeString: Osmosis not supported yet.");
} else if (chainId === CHAIN_ID_SUI) {
throw Error("hexToNativeString: Sui not supported yet.");
if (!isValidSuiType(address) && isValidSuiAddress(address)) {
return uint8ArrayToHex(
zeroPad(arrayify(address, { allowMissingPrefix: true }), 32)
);
}
throw Error("hexToNativeString: Sui types not supported yet.");
} else if (chainId === CHAIN_ID_BTC) {
throw Error("hexToNativeString: Btc not supported yet.");
} else if (chainId === CHAIN_ID_APTOS) {
@ -258,7 +265,9 @@ export const tryNativeToHexString = (
return getExternalAddressFromType(address);
}
return uint8ArrayToHex(zeroPad(arrayify(address, { allowMissingPrefix:true }), 32));
return uint8ArrayToHex(
zeroPad(arrayify(address, { allowMissingPrefix: true }), 32)
);
} else if (chainId === CHAIN_ID_UNSET) {
throw Error("hexToNativeString: Chain id unset");
} else {

View File

@ -181,8 +181,9 @@ const MAINNET = {
"0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130",
},
sui: {
core: undefined,
token_bridge: undefined,
core: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
token_bridge:
"0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9",
nft_bridge: undefined,
},
moonbeam: {
@ -247,7 +248,8 @@ const MAINNET = {
token_bridge: undefined,
nft_bridge: undefined,
},
sepolia: { // This is testnet only.
sepolia: {
// This is testnet only.
core: undefined,
token_bridge: undefined,
nft_bridge: undefined,
@ -352,8 +354,9 @@ const TESTNET = {
nft_bridge: undefined,
},
sui: {
core: undefined,
token_bridge: undefined,
core: "0x69ae41bdef4770895eb4e7aaefee5e4673acc08f6917b4856cf55549c4573ca8",
token_bridge:
"0x32422cb2f929b6a4e3f81b4791ea11ac2af896b310f3d9442aa1fe924ce0bab4",
nft_bridge: undefined,
},
moonbeam: {
@ -524,8 +527,9 @@ const DEVNET = {
"0x46da3d4c569388af61f951bdd1153f4c875f90c2991f6b2d0a38e2161a40852c",
},
sui: {
core: undefined,
token_bridge: undefined,
core: "0x04ca9f568b19c80b4fb429c26f7cc57b1ca97e7519ccd68af436dd2706808e01", // wormhole module State object ID
token_bridge:
"0x844b3ce3f9b2cd82cb8ad1a1962593f6a340c7bad0b4867b82a49463554883dd", // token_bridge module State object ID
nft_bridge: undefined,
},
moonbeam: {

View File

@ -4,6 +4,6 @@ export * from "./bigint";
export * from "./consts";
export * from "./createNonce";
export * from "./injective";
export * from "./keccak";
export * from "./near";
export * from "./parseVaa";
export * from "./keccak";

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,

View File

@ -3,7 +3,7 @@
set -e
# Wait for sui to start
while [[ "$(curl -X POST -H "Content-Type: application/json" -d '{ "jsonrpc":"2.0", "method":"rpc.discover","id":1 }' -s -o /dev/null -w '%{http_code}' 0.0.0.0:9000/)" != "200" ]]; do sleep 5; done
while [[ "$(curl -X POST -H "Content-Type: application/json" -d '{ "jsonrpc":"2.0", "method":"rpc.discover","id":1 }' -s -o /dev/null -w '%{http_code}' 0.0.0.0:9000/)" != "200" ]]; do sleep 1; done
# Wait for sui-faucet to start
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 0.0.0.0:5003/)" != "200" ]]; do sleep 5; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 0.0.0.0:5003/)" != "200" ]]; do sleep 1; done

View File

@ -5,9 +5,6 @@ version = 0
dependencies = [
{ name = "Sui" },
]
dev-dependencies = [
{ name = "Wormhole" },
]

View File

@ -0,0 +1,15 @@
[package]
name = "TokenBridge"
version = "0.2.0"
published-at = "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d"
[dependencies.Sui]
git = "https://github.com/MystenLabs/sui.git"
subdir = "crates/sui-framework/packages/sui-framework"
rev = "09b2081498366df936abae26eea4b2d5cafb2788"
[dependencies.Wormhole]
local = "../wormhole"
[addresses]
token_bridge = "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d"

View File

@ -0,0 +1,12 @@
[package]
name = "Wormhole"
version = "0.2.0"
published-at = "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a"
[dependencies.Sui]
git = "https://github.com/MystenLabs/sui.git"
subdir = "crates/sui-framework/packages/sui-framework"
rev = "09b2081498366df936abae26eea4b2d5cafb2788"
[addresses]
wormhole = "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a"

View File

@ -15,7 +15,7 @@ module wormhole::governance_message {
/// Guardian set used to sign VAA did not use current Guardian set.
const E_OLD_GUARDIAN_SET_GOVERNANCE: u64 = 0;
/// Governance chain disagrees does not match.
/// Governance chain does not match.
const E_INVALID_GOVERNANCE_CHAIN: u64 = 1;
/// Governance emitter address does not match.
const E_INVALID_GOVERNANCE_EMITTER: u64 = 2;

View File

@ -232,6 +232,7 @@ async function main() {
near: String(process.env.REGISTER_NEAR_TOKEN_BRIDGE_VAA),
terra2: String(process.env.REGISTER_TERRA2_TOKEN_BRIDGE_VAA),
aptos: String(process.env.REGISTER_APTOS_TOKEN_BRIDGE_VAA),
sui: String(process.env.REGISTER_SUI_TOKEN_BRIDGE_VAA),
};
const instantiateMsg = {};