sdk/js: add injective

This commit is contained in:
Evan Gray 2022-06-29 17:09:29 +00:00 committed by Paul Noel
parent 34355fd234
commit 239b22e3a8
22 changed files with 4759 additions and 314 deletions

View File

@ -208,12 +208,14 @@ const TESTNET = {
deployerAccount: undefined,
},
injective: {
rpc: undefined,
key: undefined,
rpc: "https://k8s.testnet.tm.injective.network:443",
chain_id: "injective-888",
key: get_env_var("ETH_KEY_TESTNET"),
},
osmosis: {
rpc: undefined,
key: undefined,
chain_id: "osmo-test-4",
key: get_env_var("ETH_KEY_TESTNET"),
},
aptos: {
rpc: undefined,

3593
sdk/js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -39,6 +39,8 @@
"license": "Apache-2.0",
"devDependencies": {
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
"@injectivelabs/networks": "^1.0.12",
"@injectivelabs/tx-ts": "^1.0.22",
"@openzeppelin/contracts": "^4.2.0",
"@typechain/ethers-v5": "^7.0.1",
"@types/jest": "^27.0.2",
@ -58,6 +60,7 @@
"dependencies": {
"@certusone/wormhole-sdk-proto-web": "^0.0.5",
"@certusone/wormhole-sdk-wasm": "^0.0.1",
"@injectivelabs/sdk-ts": "^1.0.75",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.24.0",
"@terra-money/terra.js": "^3.1.3",

View File

@ -366,7 +366,7 @@ export async function signSendAndConfirmAlgorand(
const result = await waitForConfirmation(
algodClient,
txs[txs.length - 1].tx.txID(),
1
4
);
return result;
}

View File

@ -25,6 +25,8 @@ export async function getEmitterAddressTerra(programAddress: string) {
).toString("hex");
}
export const getEmitterAddressInjective = getEmitterAddressTerra;
export function getEmitterAddressAlgorand(appId: bigint): string {
const appAddr: string = getApplicationAddress(appId);
const decAppAddr: Uint8Array = decodeAddress(appAddr).publicKey;

View File

@ -67,6 +67,22 @@ export function parseSequencesFromLogTerra(info: TxInfo): string[] {
return sequences;
}
export function parseSequenceFromLogInjective(info: any): string {
// Scan for the Sequence attribute in all the outputs of the transaction.
let sequence = "";
const jsonLog = JSON.parse(info.rawLog);
jsonLog.map((row: any) => {
row.events.map((event: any) => {
event.attributes.map((attribute: any) => {
if (attribute.key === "message.sequence") {
sequence = attribute.value;
}
});
});
});
return sequence.toString();
}
const SOLANA_SEQ_LOG = "Program log: Sequence: ";
export function parseSequenceFromLogSolana(info: TransactionResponse) {
// TODO: better parsing, safer

View File

@ -1,9 +1,36 @@
import { keccak256 } from "ethers/lib/utils";
import { isNativeDenom } from "../terra";
import {
CHAIN_ID_INJECTIVE,
CHAIN_ID_TERRA,
coalesceCosmWasmChainId,
CosmWasmChainId,
CosmWasmChainName,
isTerraChain,
} from "../utils";
export function buildTokenId(address: string) {
export const isNativeDenomInjective = (string = "") => string === "inj";
export function isNativeCosmWasmDenom(
chainId: CosmWasmChainId,
address: string
) {
return (
(isNativeDenom(address) ? "01" : "00") +
(isTerraChain(chainId) && isNativeDenom(address)) ||
(chainId === CHAIN_ID_INJECTIVE && isNativeDenomInjective(address))
);
}
export function buildTokenId(
chain: Exclude<
CosmWasmChainId | CosmWasmChainName,
typeof CHAIN_ID_TERRA | "terra"
>,
address: string
) {
const chainId: CosmWasmChainId = coalesceCosmWasmChainId(chain);
return (
(isNativeCosmWasmDenom(chainId, address) ? "01" : "00") +
keccak256(Buffer.from(address, "utf-8")).substring(4)
);
}

View File

@ -0,0 +1 @@
export * from "./address";

View File

@ -0,0 +1,872 @@
require("dotenv").config({ path: ".env" });
import { getNetworkInfo, Network } from "@injectivelabs/networks";
import {
ChainGrpcWasmApi,
ChainRestAuthApi,
DEFAULT_STD_FEE,
privateKeyToPublicKeyBase64,
} from "@injectivelabs/sdk-ts";
import { createTransaction, MsgArg, TxGrpcClient } from "@injectivelabs/tx-ts";
import { PrivateKey } from "@injectivelabs/sdk-ts/dist/local";
import { expect, test } from "@jest/globals";
import {
attestFromAlgorand,
attestFromInjective,
CHAIN_ID_ALGORAND,
CHAIN_ID_INJECTIVE,
coalesceChainId,
CONTRACTS,
createWrappedOnAlgorand,
createWrappedOnInjective,
getEmitterAddressAlgorand,
getEmitterAddressInjective,
getForeignAssetInjective,
getIsTransferCompletedAlgorand,
getIsTransferCompletedInjective,
getOriginalAssetInjective,
getSignedVAAWithRetry,
hexToUint8Array,
parseSequenceFromLogAlgorand,
parseSequenceFromLogInjective,
redeemOnAlgorand,
redeemOnInjective,
safeBigIntToNumber,
textToUint8Array,
transferFromAlgorand,
transferFromInjective,
tryHexToNativeString,
tryNativeToHexString,
tryNativeToUint8Array,
uint8ArrayToHex,
} from "..";
import { CLUSTER } from "../token_bridge/__tests__/consts";
import algosdk, {
Account,
Algodv2,
decodeAddress,
makeApplicationCallTxnFromObject,
mnemonicToSecretKey,
OnApplicationComplete,
waitForConfirmation,
} from "algosdk";
import {
getBalances,
getForeignAssetFromVaaAlgorand,
signSendAndConfirmAlgorand,
} from "../algorand/__tests__/testHelpers";
import { _parseVAAAlgorand } from "../algorand";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import { fromUint8Array } from "js-base64";
function getEndPoint() {
return CLUSTER === "mainnet" ? Network.MainnetK8s : Network.TestnetK8s;
}
test.skip("testnet - injective contract is own admin", async () => {
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
const coreQueryResult = await client.fetchContractInfo(
CONTRACTS.TESTNET.injective.core
);
expect(coreQueryResult?.admin).toEqual(CONTRACTS.TESTNET.injective.core);
const tbQueryResult = await client.fetchContractInfo(
CONTRACTS.TESTNET.injective.token_bridge
);
expect(tbQueryResult?.admin).toEqual(
CONTRACTS.TESTNET.injective.token_bridge
);
});
test.skip("testnet - injective query guardian_set_info", async () => {
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
// https://k8s.testnet.lcd.injective.network/cosmwasm/wasm/v1/contract/inj1xx3aupmgv3ce537c0yce8zzd3sz567syuyedpg/smart/eyJndWFyZGlhbl9zZXRfaW5mbyI6e319
const queryResult = await client.fetchSmartContractState(
CONTRACTS.TESTNET.injective.core,
Buffer.from('{"guardian_set_info":{}}').toString("base64")
);
let result: any = null;
if (typeof queryResult.data === "string") {
result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
}
expect(result?.guardian_set_index).toEqual(0);
expect(result?.addresses.length).toEqual(1);
});
test.skip("testnet - injective query state", async () => {
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
const queryResult = await client.fetchSmartContractState(
CONTRACTS.TESTNET.injective.core,
Buffer.from('{"get_state":{}}').toString("base64")
);
let result: any = null;
if (typeof queryResult.data === "string") {
result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
}
expect(result?.fee?.denom).toEqual("inj");
expect(result?.fee?.amount).toEqual("0");
});
test.skip("testnet - injective query token bridge", async () => {
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
// const wrappedAsset = await getIsWrappedAssetInjective(
// CONTRACTS.TESTNET.injective.token_bridge,
// "inj10jc4vr9vfq0ykkmfvfgz430w8z6hwdlqhdw9l8"
// );
// console.log("isWrappedAsset", wrappedAsset);
const queryResult = await client.fetchSmartContractState(
CONTRACTS.TESTNET.injective.token_bridge,
Buffer.from(
JSON.stringify({
wrapped_registry: {
chain: coalesceChainId("injective"),
address: tryNativeToHexString(
"inj10jc4vr9vfq0ykkmfvfgz430w8z6hwdlqhdw9l8",
"injective"
),
},
})
).toString("base64")
);
let result: any = null;
if (typeof queryResult.data === "string") {
result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
console.log("result", result);
}
});
test.skip("testnet - injective attest native asset", async () => {
// Local consts
const tba = CONTRACTS.TESTNET.injective.token_bridge;
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
// Set up Inj wallet
const walletPKHash: string = process.env.ETH_KEY || "";
const walletPK = PrivateKey.fromPrivateKey(walletPKHash);
const walletInjAddr = walletPK.toBech32();
const walletPublicKey = privateKeyToPublicKeyBase64(
Buffer.from(walletPKHash, "hex")
);
const accountDetails = await new ChainRestAuthApi(
network.sentryHttpApi
).fetchAccount(walletInjAddr);
// Attest native inj
const result = await attestFromInjective(tba, walletInjAddr, "inj");
console.log("token", JSON.stringify(result.params.msg));
// Create the transaction
console.log("creating transaction...");
const { signBytes, txRaw } = createTransaction({
message: result.toDirectSign(),
memo: "",
fee: DEFAULT_STD_FEE,
pubKey: walletPublicKey,
sequence: parseInt(accountDetails.account.base_account.sequence, 10),
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
/** Sign transaction */
const signature = await walletPK.sign(Buffer.from(signBytes));
/** Append Signatures */
txRaw.setSignaturesList([signature]);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
console.log("Simulating transaction...");
/** Simulate transaction */
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
/** Broadcast transaction */
const txResponse = await txService.broadcast();
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
// Need to get the VAA and parse it.
const logSeq: string = parseSequenceFromLogInjective(txResponse);
console.log("logSeq:", logSeq);
const emitterAddress = await getEmitterAddressInjective(
CONTRACTS.TESTNET.injective.token_bridge
);
const rpc: string[] = ["https://wormhole-v2-testnet-api.certus.one"];
const { vaaBytes: nativeAssetVaa } = await getSignedVAAWithRetry(
rpc,
CHAIN_ID_INJECTIVE,
emitterAddress,
logSeq,
{
transport: NodeHttpTransport(), //This should only be needed when running in node.
},
1000, //retryTimeout
1000 //Maximum retry attempts
);
console.log("signed VAA", uint8ArrayToHex(nativeAssetVaa));
const parsedVAA = _parseVAAAlgorand(nativeAssetVaa);
console.log("parsed attestation vaa", parsedVAA);
const assetIdFromVaa = parsedVAA.Contract || "";
console.log("assetIdFromVaa:", assetIdFromVaa);
// const origAsset = await getOriginalAssetInjective(
// assetIdFromVaa,
// // tryHexToNativeString(assetIdFromVaa, "injective"),
// client
// );
// console.log("origAsset:", origAsset);
// const natString = tryHexToNativeString(
// uint8ArrayToHex(origAsset.assetAddress),
// "injective"
// );
// console.log("natString:", natString);
});
test.skip("testnet - injective attest foreign asset", async () => {
const tba = CONTRACTS.TESTNET.injective.token_bridge;
const wallet = "inj180rl9ezc4389t72pc3vvlkxxs5d9jx60w9eeu3";
const foreignAssetAddress = "inj13772jvadyx4j0hrlfh4jzk0v39k8uyfxrfs540";
const result = await attestFromInjective(tba, wallet, foreignAssetAddress);
console.log("token", JSON.stringify(result.params.msg));
console.log("json", result.toJSON());
const walletPKHash = process.env.ETH_KEY || "";
const walletPK = PrivateKey.fromPrivateKey(walletPKHash);
const walletInjAddr = walletPK.toBech32();
const walletPublicKey = privateKeyToPublicKeyBase64(
Buffer.from(walletPKHash, "hex")
);
const network = getNetworkInfo(getEndPoint());
/** Account Details **/
const accountDetails = await new ChainRestAuthApi(
network.sentryHttpApi
).fetchAccount(walletInjAddr);
const { signBytes, txRaw } = createTransaction({
message: result.toDirectSign(),
memo: "",
fee: DEFAULT_STD_FEE,
pubKey: walletPublicKey,
sequence: parseInt(accountDetails.account.base_account.sequence, 10),
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
/** Sign transaction */
const signature = await walletPK.sign(Buffer.from(signBytes));
/** Append Signatures */
txRaw.setSignaturesList([signature]);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
/** Simulate transaction */
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
/** Broadcast transaction */
const txResponse = await txService.broadcast();
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
// expect(result?.fee?.denom).toEqual("inj");
// expect(result?.fee?.amount).toEqual("0");
});
test.skip("testnet - injective get foreign asset", async () => {
const tba = CONTRACTS.TESTNET.injective.token_bridge;
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
// const foreignAssetAddress = "inj10jc4vr9vfq0ykkmfvfgz430w8z6hwdlqhdw9l8";
const foreignAssetAddress = "inj13772jvadyx4j0hrlfh4jzk0v39k8uyfxrfs540";
const ateArray = tryNativeToUint8Array(foreignAssetAddress, "injective");
console.log("wormhole address:", ateArray);
const result = await getForeignAssetInjective(
tba,
client,
CHAIN_ID_INJECTIVE,
ateArray
);
console.log("result", result);
expect(result?.length).toBeGreaterThan(0);
});
test("testnet - injective submit a vaa", async () => {
try {
// Set up Algorand side
const algodToken = "";
const algodServer = "https://testnet-api.algonode.cloud";
const algodPort = "";
const algodClient = new Algodv2(algodToken, algodServer, algodPort);
console.log("Doing Algorand part......");
console.log("Creating wallet...");
const algoWallet: Account = mnemonicToSecretKey(
process.env.ALGO_MNEMONIC || ""
);
console.log("wallet", algoWallet);
const accountInfo = await algodClient
.accountInformation(algoWallet.addr)
.do();
console.log("accountInfo", accountInfo);
// Attest native ALGO on Algorand
// Asset Index of native ALGO is 0
const AlgoIndex = BigInt(0);
const CoreID = BigInt(86525623); // Testnet
const TokenBridgeID = BigInt(86525641); // Testnet
const b = await getBalances(algodClient, algoWallet.addr);
console.log("balances", b);
const txs = await attestFromAlgorand(
algodClient,
TokenBridgeID,
CoreID,
algoWallet.addr,
AlgoIndex
);
console.log("txs", txs);
const result = await signSendAndConfirmAlgorand(
algodClient,
txs,
algoWallet
);
console.log("result", result);
const sn = parseSequenceFromLogAlgorand(result);
console.log("sn", sn);
// Now, try to send a NOP
const suggParams: algosdk.SuggestedParams = await algodClient
.getTransactionParams()
.do();
const nopTxn = makeApplicationCallTxnFromObject({
from: algoWallet.addr,
appIndex: safeBigIntToNumber(TokenBridgeID),
onComplete: OnApplicationComplete.NoOpOC,
appArgs: [textToUint8Array("nop")],
suggestedParams: suggParams,
});
const resp = await algodClient
.sendRawTransaction(nopTxn.signTxn(algoWallet.sk))
.do();
await waitForConfirmation(algodClient, resp.txId, 4);
// End of NOP
// Attestation on Algorand is complete. Get the VAA
// Guardian part
const rpc: string[] = ["https://wormhole-v2-testnet-api.certus.one"];
const emitterAddr = getEmitterAddressAlgorand(BigInt(TokenBridgeID));
const { vaaBytes } = await getSignedVAAWithRetry(
rpc,
CHAIN_ID_ALGORAND,
emitterAddr,
sn,
{ transport: NodeHttpTransport() }
);
const pvaa = _parseVAAAlgorand(vaaBytes);
console.log("parsed vaa", pvaa);
// Submit the VAA on the Injective side
// Start of Injective side
console.log("Start doing the Injective part......");
const tba = CONTRACTS.TESTNET.injective.token_bridge;
const walletPKHash = process.env.ETH_KEY || "";
const walletPK = PrivateKey.fromPrivateKey(walletPKHash);
const walletInjAddr = walletPK.toBech32();
const walletPublicKey = privateKeyToPublicKeyBase64(
Buffer.from(walletPKHash, "hex")
);
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
console.log("Getting account details...");
const accountDetails = await new ChainRestAuthApi(
network.sentryHttpApi
).fetchAccount(walletInjAddr);
console.log("createWrappedOnInjective...", vaaBytes);
const msg = await createWrappedOnInjective(tba, walletInjAddr, vaaBytes);
console.log("cr", msg);
console.log("submit_vaa", JSON.stringify(msg.params.msg));
/** Prepare the Transaction **/
console.log("create transaction...");
const txFee = DEFAULT_STD_FEE;
txFee.amount[0] = { amount: "250000000000000", denom: "inj" };
txFee.gas = "500000";
const { signBytes, txRaw } = createTransaction({
message: msg.toDirectSign(),
memo: "",
fee: txFee,
pubKey: walletPublicKey,
sequence: parseInt(accountDetails.account.base_account.sequence, 10),
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
console.log("txRaw", txRaw);
console.log("sign transaction...");
/** Sign transaction */
const signature = await walletPK.sign(Buffer.from(signBytes));
/** Append Signatures */
txRaw.setSignaturesList([signature]);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
console.log("simulate transaction...");
/** Simulate transaction */
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
console.log("broadcast transaction...");
/** Broadcast transaction */
const txResponse = await txService.broadcast();
console.log("txResponse", txResponse);
if (txResponse.code !== 0) {
console.log(`Transaction failed: ${txResponse.rawLog}`);
} else {
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
}
const contract = pvaa.Contract || "0";
console.log("contract", contract);
const fa = await getForeignAssetInjective(
tba,
client,
"algorand",
hexToUint8Array(contract)
);
console.log("fa", fa);
const forAsset = fa || "";
// attested Algo contract = inj10jc4vr9vfq0ykkmfvfgz430w8z6hwdlqhdw9l8
// Start transfer from Algorand to Injective
const AmountToTransfer: number = 12300;
const Fee: number = 0;
console.log("About to transferFromAlgorand");
const transferTxs = await transferFromAlgorand(
algodClient,
TokenBridgeID,
CoreID,
algoWallet.addr,
AlgoIndex,
BigInt(AmountToTransfer),
tryNativeToHexString(walletInjAddr, "injective"),
CHAIN_ID_INJECTIVE,
BigInt(Fee)
);
console.log("About to signSendAndConfirm");
const transferResult = await signSendAndConfirmAlgorand(
algodClient,
transferTxs,
algoWallet
);
console.log("About to parseSeqFromLog");
const txSid = parseSequenceFromLogAlgorand(transferResult);
console.log("About to getSignedVAA");
const signedVaa = await getSignedVAAWithRetry(
rpc,
CHAIN_ID_ALGORAND,
emitterAddr,
txSid,
{ transport: NodeHttpTransport() }
);
const pv = _parseVAAAlgorand(signedVaa.vaaBytes);
console.log("vaa", pv);
console.log("About to redeemOnInjective");
const roi = await redeemOnInjective(tba, walletInjAddr, signedVaa.vaaBytes);
console.log("roi", roi);
{
const accountDetails = await new ChainRestAuthApi(
network.sentryHttpApi
).fetchAccount(walletInjAddr);
const { signBytes, txRaw } = createTransaction({
message: roi.toDirectSign(),
memo: "",
fee: txFee,
pubKey: walletPublicKey,
sequence: parseInt(accountDetails.account.base_account.sequence, 10),
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
console.log("txRaw", txRaw);
console.log("sign transaction...");
/** Sign transaction */
const sig = await walletPK.sign(Buffer.from(signBytes));
/** Append Signatures */
txRaw.setSignaturesList([sig]);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
console.log("simulate transaction...");
/** Simulate transaction */
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
console.log("broadcast transaction...");
/** Broadcast transaction */
const txResponse = await txService.broadcast();
console.log("txResponse", txResponse);
if (txResponse.code !== 0) {
console.log(`Transaction failed: ${txResponse.rawLog}`);
} else {
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
}
}
console.log("Checking if transfer is completed");
expect(
await getIsTransferCompletedInjective(tba, signedVaa.vaaBytes, client)
).toBe(true);
{
console.log("checking vaa:", signedVaa.vaaBytes);
console.log("checking vaa:", uint8ArrayToHex(signedVaa.vaaBytes));
const network = getNetworkInfo(getEndPoint());
const client = new ChainGrpcWasmApi(network.sentryGrpcApi);
const queryResult = await client.fetchSmartContractState(
CONTRACTS.TESTNET.injective.token_bridge,
Buffer.from(
JSON.stringify({
transfer_info: {
vaa: fromUint8Array(signedVaa.vaaBytes),
},
})
).toString("base64")
);
let result: any = null;
let addr: string = "";
if (typeof queryResult.data === "string") {
result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
console.log("result", result);
addr = tryHexToNativeString(
uint8ArrayToHex(result.recipient),
"injective"
);
console.log("Injective address?", addr);
}
// interface wAlgoBalance {
// balance: string;
// }
console.log(
"Getting balance for foreign asset",
forAsset,
"on address",
walletInjAddr
);
const balRes = await client.fetchSmartContractState(
forAsset,
Buffer.from(
JSON.stringify({
balance: {
address: walletInjAddr,
},
})
).toString("base64")
);
result = null;
console.log("balRes", balRes);
if (typeof balRes.data === "string") {
result = JSON.parse(
Buffer.from(balRes.data, "base64").toString("utf-8")
);
console.log("balRes", result);
}
}
} catch (e) {
console.error(e);
}
});
test.skip("Attest and transfer token from Injective to Algorand", async () => {
const Asset: string = "inj";
const walletPKHash: string = process.env.INJ_PK_HASH || "";
const walletPK = PrivateKey.fromPrivateKey(walletPKHash);
const walletInjAddr = walletPK.toBech32();
const walletPublicKey = privateKeyToPublicKeyBase64(
Buffer.from(walletPKHash, "hex")
);
const network = getNetworkInfo(getEndPoint());
console.log("create transaction...");
const txFee = DEFAULT_STD_FEE;
txFee.amount[0] = { amount: "250000000000000", denom: "inj" };
txFee.gas = "500000";
// Attest
const attestMsg = await attestFromInjective(
CONTRACTS.TESTNET.injective.token_bridge,
walletInjAddr,
Asset
);
const accountDetails = await new ChainRestAuthApi(
network.sentryHttpApi
).fetchAccount(walletInjAddr);
const { signBytes, txRaw } = createTransaction({
message: attestMsg.toDirectSign(),
memo: "",
fee: txFee,
pubKey: walletPublicKey,
sequence: parseInt(accountDetails.account.base_account.sequence, 10),
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
console.log("txRaw", txRaw);
console.log("sign transaction...");
/** Sign transaction */
const signedMsg = await walletPK.sign(Buffer.from(signBytes));
/** Append Signatures */
txRaw.setSignaturesList([signedMsg]);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
console.log("simulate transaction...");
/** Simulate transaction */
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
console.log("broadcast transaction...");
/** Broadcast transaction */
const txResponse = await txService.broadcast();
console.log("txResponse", txResponse);
if (txResponse.code !== 0) {
console.log(`Transaction failed: ${txResponse.rawLog}`);
} else {
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
}
console.log("txResponse", JSON.stringify(txResponse.rawLog));
console.log("txResponse", txResponse.rawLog);
const sequence = parseSequenceFromLogInjective(txResponse);
if (!sequence) {
throw new Error("Sequence not found");
}
console.log("found seqNum:", sequence);
const emitterAddress = await getEmitterAddressInjective(
CONTRACTS.TESTNET.injective.token_bridge
);
const rpc: string[] = ["https://wormhole-v2-testnet-api.certus.one"];
const { vaaBytes: attestSignedVaa } = await getSignedVAAWithRetry(
rpc,
CHAIN_ID_INJECTIVE,
emitterAddress,
sequence,
{
transport: NodeHttpTransport(), //This should only be needed when running in node.
},
1000, //retryTimeout
1000 //Maximum retry attempts
);
console.log("signed VAA", uint8ArrayToHex(attestSignedVaa));
console.log("parsed attestation vaa", _parseVAAAlgorand(attestSignedVaa));
const algodToken = "";
const algodServer = "https://testnet-api.algonode.cloud";
const algodPort = "";
const algodClient = new Algodv2(algodToken, algodServer, algodPort);
console.log("Doing Algorand part......");
console.log("Creating wallet...");
if (!process.env.ALGO_MNEMONIC) {
throw new Error("Failed to read in ALGO_MNEMONIC");
}
const algoWallet: Account = mnemonicToSecretKey(process.env.ALGO_MNEMONIC);
const CoreID = BigInt(86525623); // Testnet
const TokenBridgeID = BigInt(86525641); // Testnet
console.log("createWrappedOnAlgorand...");
const createWrappedTxs = await createWrappedOnAlgorand(
algodClient,
TokenBridgeID,
CoreID,
algoWallet.addr,
attestSignedVaa
);
console.log("signing and sending to algorand...");
const sscResult = await signSendAndConfirmAlgorand(
algodClient,
createWrappedTxs,
algoWallet
);
console.log("sscResult", sscResult);
console.log("getting foreign asset:");
let assetIdCreated = await getForeignAssetFromVaaAlgorand(
algodClient,
TokenBridgeID,
attestSignedVaa
);
if (!assetIdCreated) {
throw new Error("Failed to create asset");
}
console.log("assetId:", assetIdCreated);
// Transfer
const transferMsgs = await transferFromInjective(
walletInjAddr,
CONTRACTS.TESTNET.injective.token_bridge,
"inj",
"1000000",
CHAIN_ID_ALGORAND,
decodeAddress(algoWallet.addr).publicKey
);
console.log("number of msgs = ", transferMsgs.length);
let xferMsgsSigned: MsgArg[] = transferMsgs.map((element) =>
element.toDirectSign()
);
{
console.log("xferMsgsSigned", xferMsgsSigned);
const { signBytes, txRaw } = createTransaction({
message: xferMsgsSigned,
memo: "",
fee: txFee,
pubKey: walletPublicKey,
sequence:
parseInt(accountDetails.account.base_account.sequence, 10) +
xferMsgsSigned.length -
1,
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
console.log("txRaw", txRaw);
console.log("sign transaction...");
/** Sign transaction */
const signedMsg = await walletPK.sign(Buffer.from(signBytes));
/** Append Signatures */
txRaw.setSignaturesList([signedMsg]);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
console.log("simulate transaction...");
/** Simulate transaction */
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
console.log("broadcast transaction...");
/** Broadcast transaction */
const txResponse = await txService.broadcast();
console.log("txResponse", txResponse);
if (txResponse.code !== 0) {
console.log(`Transaction failed: ${txResponse.rawLog}`);
} else {
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
}
console.log("txResponse", JSON.stringify(txResponse));
const sequence = parseSequenceFromLogInjective(txResponse);
if (!sequence) {
throw new Error("Sequence not found");
}
console.log("found seqNum:", sequence);
const { vaaBytes } = await getSignedVAAWithRetry(
rpc,
CHAIN_ID_INJECTIVE,
emitterAddress,
sequence,
{
transport: NodeHttpTransport(), //This should only be needed when running in node.
},
1000, //retryTimeout
1000 //Maximum retry attempts
);
console.log("parsed VAA", _parseVAAAlgorand(vaaBytes));
console.log("About to redeemOnAlgorand...");
const tids = await redeemOnAlgorand(
algodClient,
TokenBridgeID,
CoreID,
vaaBytes,
algoWallet.addr
);
console.log("After redeem...", tids);
const resToLog = await signSendAndConfirmAlgorand(
algodClient,
tids,
algoWallet
);
console.log("resToLog", resToLog["confirmed-round"]);
console.log("Checking if isRedeemed...");
const success = await getIsTransferCompletedAlgorand(
algodClient,
TokenBridgeID,
vaaBytes
);
expect(success).toBe(true);
}
const balances = await getBalances(algodClient, algoWallet.addr);
console.log("Ending balances", balances);
});

View File

@ -0,0 +1,108 @@
import { getNetworkInfo, Network } from "@injectivelabs/networks";
import {
ChainRestAuthApi,
DEFAULT_STD_FEE,
MsgExecuteContract,
privateKeyToPublicKeyBase64,
} from "@injectivelabs/sdk-ts";
import {
createTransaction,
TxClient,
TxGrpcClient,
} from "@injectivelabs/tx-ts";
import { PrivateKey } from "@injectivelabs/sdk-ts/dist/local";
import { test } from "@jest/globals";
import { CONTRACTS } from "..";
test.skip("testnet - injective attest native token", async () => {
const network = getNetworkInfo(Network.TestnetK8s);
console.log("Using network:", network);
const privateKeyHash = process.env.ETH_KEY || "";
const privateKey = PrivateKey.fromPrivateKey(privateKeyHash);
const injectiveAddress = privateKey.toBech32();
console.log("Using wallet:", injectiveAddress);
const publicKey = privateKeyToPublicKeyBase64(
Buffer.from(privateKeyHash, "hex")
);
const isNativeAsset = true;
const asset = "inj";
const nonce = 69;
console.log("Account details");
/** Account Details **/
const accountDetails = await new ChainRestAuthApi(
network.sentryHttpApi
).fetchAccount(injectiveAddress);
console.log(accountDetails);
/** Prepare the Message */
console.log("Prepare the message");
const msg = MsgExecuteContract.fromJSON({
contractAddress: CONTRACTS.TESTNET.injective.token_bridge,
sender: injectiveAddress,
msg: {
asset_info: isNativeAsset
? {
native_token: { denom: asset },
}
: {
token: {
contract_addr: asset,
},
},
nonce: nonce,
},
action: "create_asset_meta",
});
/** Prepare the Transaction **/
console.log("Prepare the transaction");
const { signBytes, txRaw } = createTransaction({
message: msg.toDirectSign(),
memo: "",
fee: DEFAULT_STD_FEE,
pubKey: publicKey,
sequence: parseInt(accountDetails.account.base_account.sequence, 10),
accountNumber: parseInt(
accountDetails.account.base_account.account_number,
10
),
chainId: network.chainId,
});
/** Sign transaction */
console.log("Sign transaction");
const signature = await privateKey.sign(Buffer.from(signBytes));
/** Append Signatures */
console.log("Append signatures");
txRaw.setSignaturesList([signature]);
/** Calculate hash of the transaction */
console.log("Calculate hash");
console.log(`Transaction Hash: ${await TxClient.hash(txRaw)}`);
const txService = new TxGrpcClient({
txRaw,
endpoint: network.sentryGrpcApi,
});
/** Simulate transaction */
console.log("Simulate transaction");
const simulationResponse = await txService.simulate();
console.log(
`Transaction simulation response: ${JSON.stringify(
simulationResponse.gasInfo
)}`
);
/** Broadcast transaction */
console.log("Broadcast transaction");
const txResponse = await txService.broadcast();
console.log(
`Broadcasted transaction hash: ${JSON.stringify(txResponse.txhash)}`
);
console.log(txResponse);
});

View File

@ -1,5 +1,6 @@
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { MsgExecuteContract as MsgExecuteContractInjective } from "@injectivelabs/sdk-ts";
import {
Algodv2,
bigIntToBytes,
@ -24,6 +25,7 @@ import { createNonce } from "../utils/createNonce";
import { parseSequenceFromLogNear } from "../bridge/parseSequenceFromLog";
import { getIsWrappedAssetNear } from ".";
import { isNativeDenomInjective } from "../cosmwasm";
export async function attestFromEth(
tokenBridgeAddress: string,
@ -60,6 +62,41 @@ export async function attestFromTerra(
});
}
/**
* Creates attestation message
* @param tokenBridgeAddress Address of Inj token bridge contract
* @param walletAddress Address of wallet in inj format
* @param asset Name or address of the asset to be attested
* For native assets the asset string is the denomination.
* For foreign assets the asset string is the inj address of the foreign asset
* @returns Message to be broadcast
*/
export async function attestFromInjective(
tokenBridgeAddress: string,
walletAddress: string,
asset: string
): Promise<MsgExecuteContractInjective> {
const nonce = Math.round(Math.random() * 100000);
const isNativeAsset = isNativeDenomInjective(asset);
return MsgExecuteContractInjective.fromJSON({
contractAddress: tokenBridgeAddress,
sender: walletAddress,
msg: {
asset_info: isNativeAsset
? {
native_token: { denom: asset },
}
: {
token: {
contract_addr: asset,
},
},
nonce: nonce,
},
action: "create_asset_meta",
});
}
export async function attestFromSolana(
connection: Connection,
bridgeAddress: string,

View File

@ -7,7 +7,11 @@ import { TransactionSignerPair, _submitVAAAlgorand } from "../algorand";
import { Bridge__factory } from "../ethers-contracts";
import { ixFromRust } from "../solana";
import { importTokenWasm } from "../solana/wasm";
import { Account as nearAccount, providers as nearProviders } from "near-api-js";
import { submitVAAOnInjective } from "./redeem";
import {
Account as nearAccount,
providers as nearProviders,
} from "near-api-js";
import BN from "bn.js";
export async function createWrappedOnEth(
@ -34,6 +38,8 @@ export async function createWrappedOnTerra(
});
}
export const createWrappedOnInjective = submitVAAOnInjective;
export async function createWrappedOnSolana(
connection: Connection,
bridgeAddress: string,

View File

@ -1,5 +1,7 @@
import { Connection, PublicKey } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts";
import { getNetworkInfo, Network } from "@injectivelabs/networks";
import { Algodv2 } from "algosdk";
import { ethers } from "ethers";
import { fromUint8Array } from "js-base64";
@ -66,6 +68,44 @@ export async function getForeignAssetTerra(
}
}
/**
* Returns the address of the foreign asset
* @param tokenBridgeAddress Address of token bridge contact
* @param client Holds the wallet and signing information
* @param originChain The chainId of the origin of the asset
* @param originAsset The address of the origin asset
* @returns The foreign asset address or null
*/
export async function getForeignAssetInjective(
tokenBridgeAddress: string,
client: ChainGrpcWasmApi,
originChain: ChainId | ChainName,
originAsset: Uint8Array
): Promise<string | null> {
try {
const queryResult = await client.fetchSmartContractState(
tokenBridgeAddress,
Buffer.from(
JSON.stringify({
wrapped_registry: {
chain: coalesceChainId(originChain),
address: fromUint8Array(originAsset),
},
})
).toString("base64")
);
let result: any = null;
if (typeof queryResult.data === "string") {
result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
}
return result.address;
} catch (e) {
return null;
}
}
/**
* Returns a foreign asset address on Solana for a provided native chain and asset address
* @param connection

View File

@ -1,3 +1,4 @@
import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts";
import { Connection, PublicKey } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { Algodv2, bigIntToBytes } from "algosdk";
@ -80,7 +81,7 @@ export async function getIsTransferCompletedTerra(
export async function getIsTransferCompletedTerra2(
tokenBridgeAddress: string,
signedVAA: Uint8Array,
client: LCDClient,
client: LCDClient
): Promise<boolean> {
const result: { is_redeemed: boolean } = await client.wasm.contractQuery(
tokenBridgeAddress,
@ -93,6 +94,37 @@ export async function getIsTransferCompletedTerra2(
return result.is_redeemed;
}
/**
* Return if the VAA has been redeemed or not
* @param tokenBridgeAddress The Injective token bridge contract address
* @param signedVAA The signed VAA byte array
* @param client Holds the wallet and signing information
* @returns true if the VAA has been redeemed.
*/
export async function getIsTransferCompletedInjective(
tokenBridgeAddress: string,
signedVAA: Uint8Array,
client: ChainGrpcWasmApi
): Promise<boolean> {
const queryResult = await client.fetchSmartContractState(
tokenBridgeAddress,
Buffer.from(
JSON.stringify({
is_vaa_redeemed: {
vaa: fromUint8Array(signedVAA),
},
})
).toString("base64")
);
if (typeof queryResult.data === "string") {
const result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
return result.is_redeemed;
}
return false;
}
export async function getIsTransferCompletedSolana(
tokenBridgeAddress: string,
signedVAA: Uint8Array,

View File

@ -1,10 +1,13 @@
import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts";
import { Connection, PublicKey } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { Algodv2, getApplicationAddress } from "algosdk";
import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { importTokenWasm } from "../solana/wasm";
import { CHAIN_ID_INJECTIVE, tryNativeToHexString } from "../utils";
import { safeBigIntToNumber } from "../utils/bigint";
import { getForeignAssetInjective } from "./getForeignAsset";
/**
* Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
@ -32,6 +35,31 @@ export async function getIsWrappedAssetTerra(
return false;
}
/**
* Checks if the asset is a wrapped asset
* @param tokenBridgeAddress The address of the Injective token bridge contract
* @param client Connection/wallet information
* @param assetAddress Address of the asset in Injective format
* @returns true if asset is a wormhole wrapped asset
*/
export async function getIsWrappedAssetInjective(
tokenBridgeAddress: string,
client: ChainGrpcWasmApi,
assetAddress: string
): Promise<boolean> {
const hexified = tryNativeToHexString(assetAddress, "injective");
const result = await getForeignAssetInjective(
tokenBridgeAddress,
client,
CHAIN_ID_INJECTIVE,
new Uint8Array(Buffer.from(hexified))
);
if (result === null) {
return false;
}
return true;
}
/**
* Returns whether or not an asset on Solana is a wormhole wrapped asset
* @param connection
@ -90,5 +118,5 @@ export function getIsWrappedAssetNear(
tokenBridge: string,
asset: string
): boolean {
return asset.endsWith("." + tokenBridge);
return asset.endsWith("." + tokenBridge);
}

View File

@ -1,12 +1,14 @@
const sha256 = require("js-sha256");
import { getNetworkInfo, Network } from "@injectivelabs/networks";
import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts";
import { Connection, PublicKey } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { Algodv2 } from "algosdk";
import { ethers } from "ethers";
import { arrayify, zeroPad } from "ethers/lib/utils";
import { decodeLocalState } from "../algorand";
import { buildTokenId } from "../cosmwasm/address";
import { buildTokenId, isNativeCosmWasmDenom } from "../cosmwasm/address";
import { TokenImplementation__factory } from "../ethers-contracts";
import { importTokenWasm } from "../solana/wasm";
import { buildNativeId, isNativeDenom } from "../terra";
@ -16,10 +18,15 @@ import {
ChainName,
CHAIN_ID_ALGORAND,
CHAIN_ID_NEAR,
CHAIN_ID_INJECTIVE,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
coalesceChainId,
CosmWasmChainId,
CosmWasmChainName,
hexToUint8Array,
coalesceCosmWasmChainId,
tryHexToNativeAssetString,
} from "../utils";
import { safeBigIntToNumber } from "../utils/bigint";
import {
@ -81,20 +88,74 @@ export async function getOriginalAssetTerra(
return getOriginalAssetCosmWasm(client, wrappedAddress, CHAIN_ID_TERRA);
}
/**
* Returns information about the asset
* @param wrappedAddress Address of the asset in wormhole wrapped format (hex string)
* @param client WASM api client
* @returns Information about the asset
*/
export async function getOriginalAssetInjective(
wrappedAddress: string,
client: ChainGrpcWasmApi
): Promise<WormholeWrappedInfo> {
const chainId = CHAIN_ID_INJECTIVE;
if (isNativeCosmWasmDenom(CHAIN_ID_INJECTIVE, wrappedAddress)) {
return {
isWrapped: false,
chainId: chainId,
assetAddress: hexToUint8Array(buildTokenId(chainId, wrappedAddress)),
};
}
try {
const injWrappedAddress = tryHexToNativeAssetString(
wrappedAddress,
CHAIN_ID_INJECTIVE
);
const queryResult = await client.fetchSmartContractState(
injWrappedAddress,
Buffer.from(
JSON.stringify({
wrapped_asset_info: {},
})
).toString("base64")
);
let result: any = null;
if (typeof queryResult.data === "string") {
result = JSON.parse(
Buffer.from(queryResult.data, "base64").toString("utf-8")
);
return {
isWrapped: true,
chainId: result.asset_chain,
assetAddress: new Uint8Array(
Buffer.from(result.asset_address, "base64")
),
};
}
} catch (e) {
console.error("getOriginalAssetInjective() failed:", e);
}
return {
isWrapped: false,
chainId: chainId,
assetAddress: hexToUint8Array(buildTokenId(chainId, wrappedAddress)),
};
}
export async function getOriginalAssetCosmWasm(
client: LCDClient,
wrappedAddress: string,
lookupChain: ChainId | ChainName
lookupChain: CosmWasmChainId | CosmWasmChainName
): Promise<WormholeWrappedInfo> {
const chainId = coalesceChainId(lookupChain);
if (isNativeDenom(wrappedAddress)) {
const chainId = coalesceCosmWasmChainId(lookupChain);
if (isNativeCosmWasmDenom(chainId, wrappedAddress)) {
return {
isWrapped: false,
chainId: chainId,
assetAddress:
chainId === CHAIN_ID_TERRA
? buildNativeId(wrappedAddress)
: hexToUint8Array(buildTokenId(wrappedAddress)),
: hexToUint8Array(buildTokenId(chainId, wrappedAddress)),
};
}
try {
@ -121,7 +182,7 @@ export async function getOriginalAssetCosmWasm(
assetAddress:
chainId === CHAIN_ID_TERRA
? zeroPad(canonicalAddress(wrappedAddress), 32)
: hexToUint8Array(buildTokenId(wrappedAddress)),
: hexToUint8Array(buildTokenId(chainId, wrappedAddress)),
};
}

View File

@ -22,22 +22,19 @@ import {
MAX_VAA_DECIMALS,
WSOL_ADDRESS,
WSOL_DECIMALS,
uint8ArrayToHex
uint8ArrayToHex,
} from "../utils";
import {
getForeignAssetNear
} from ".";
import { getForeignAssetNear } from ".";
import {
_parseVAAAlgorand,
} from "../algorand";
import { _parseVAAAlgorand } from "../algorand";
import { hexToNativeString } from "../utils/array";
import { parseTransferPayload } from "../utils/parseVaa";
import { Account as nearAccount } from "near-api-js";
import BN from "bn.js";
import { providers as nearProviders } from "near-api-js";
import { MsgExecuteContract as MsgExecuteContractInjective } from "@injectivelabs/sdk-ts";
export async function redeemOnEth(
tokenBridgeAddress: string,
@ -75,6 +72,29 @@ export async function redeemOnTerra(
});
}
/**
* Submits the supplied VAA to Injective
* @param tokenBridgeAddress Address of Inj token bridge contract
* @param walletAddress Address of wallet in inj format
* @param signedVAA VAA with the attestation message
* @returns Message to be broadcast
*/
export async function submitVAAOnInjective(
tokenBridgeAddress: string,
walletAddress: string,
signedVAA: Uint8Array
): Promise<MsgExecuteContractInjective> {
return MsgExecuteContractInjective.fromJSON({
contractAddress: tokenBridgeAddress,
sender: walletAddress,
msg: {
data: fromUint8Array(signedVAA),
},
action: "submit_vaa",
});
}
export const redeemOnInjective = submitVAAOnInjective;
export async function redeemAndUnwrapOnSolana(
connection: Connection,
bridgeAddress: string,

View File

@ -7,6 +7,7 @@ import {
Transaction as SolanaTransaction,
} from "@solana/web3.js";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { MsgExecuteContract as MsgExecuteContractInjective } from "@injectivelabs/sdk-ts";
import {
Algodv2,
bigIntToBytes,
@ -18,7 +19,7 @@ import {
SuggestedParams,
Transaction as AlgorandTransaction,
} from "algosdk";
import { BigNumber, ethers, Overrides, PayableOverrides } from "ethers";
import { ethers, Overrides, PayableOverrides } from "ethers";
import { isNativeDenom } from "..";
import {
assetOptinCheck,
@ -34,7 +35,6 @@ import {
import { getBridgeFeeIx, ixFromRust } from "../solana";
import { importTokenWasm } from "../solana/wasm";
import {
CHAIN_ID_SOLANA,
ChainId,
ChainName,
WSOL_ADDRESS,
@ -43,9 +43,14 @@ import {
hexToUint8Array,
textToUint8Array,
uint8ArrayToHex,
CHAIN_ID_SOLANA,
} from "../utils";
import { safeBigIntToNumber } from "../utils/bigint";
import { Account as nearAccount, providers as nearProviders } from "near-api-js";
import { isNativeDenomInjective } from "../cosmwasm";
import {
Account as nearAccount,
providers as nearProviders,
} from "near-api-js";
import { parseSequenceFromLogNear } from "../bridge/parseSequenceFromLog";
const BN = require("bn.js");
@ -239,6 +244,95 @@ export async function transferFromTerra(
];
}
/**
* Creates the necessary messages to transfer an asset
* @param walletAddress Address of the Inj wallet
* @param tokenBridgeAddress Address of the token bridge contract
* @param tokenAddress Address of the token being transferred
* @param amount Amount of token to be transferred
* @param recipientChain Destination chain
* @param recipientAddress Destination wallet address
* @param relayerFee Relayer fee
* @param payload Optional payload
* @returns Transfer messages to be sent on chain
*/
export async function transferFromInjective(
walletAddress: string,
tokenBridgeAddress: string,
tokenAddress: string,
amount: string,
recipientChain: ChainId | ChainName,
recipientAddress: Uint8Array,
relayerFee: string = "0",
payload: Uint8Array | null = null
) {
const recipientChainId = coalesceChainId(recipientChain);
const nonce = Math.round(Math.random() * 100000);
const isNativeAsset = isNativeDenomInjective(tokenAddress);
const mk_action: string = payload
? "initiate_transfer_with_payload"
: "initiate_transfer";
const mk_initiate_transfer = (info: object) =>
payload
? {
asset: {
amount,
info,
},
recipient_chain: recipientChainId,
recipient: Buffer.from(recipientAddress).toString("base64"),
fee: relayerFee,
nonce: nonce,
payload: payload,
}
: {
asset: {
amount,
info,
},
recipient_chain: recipientChainId,
recipient: Buffer.from(recipientAddress).toString("base64"),
fee: relayerFee,
nonce: nonce,
};
return isNativeAsset
? [
MsgExecuteContractInjective.fromJSON({
contractAddress: tokenBridgeAddress,
sender: walletAddress,
msg: {},
action: "deposit_tokens",
amount: { denom: tokenAddress, amount: amount },
}),
MsgExecuteContractInjective.fromJSON({
contractAddress: tokenBridgeAddress,
sender: walletAddress,
msg: mk_initiate_transfer({ native_token: { denom: tokenAddress } }),
action: mk_action,
}),
]
: [
MsgExecuteContractInjective.fromJSON({
contractAddress: tokenBridgeAddress,
sender: walletAddress,
msg: {
spender: tokenBridgeAddress,
amount: amount,
expires: {
never: {},
},
},
action: "increase_allowance",
}),
MsgExecuteContractInjective.fromJSON({
contractAddress: tokenBridgeAddress,
sender: walletAddress,
msg: mk_initiate_transfer({ token: { contract_addr: tokenAddress } }),
action: mk_action,
}),
];
}
export async function transferNativeSol(
connection: Connection,
bridgeAddress: string,
@ -634,7 +728,7 @@ export async function transferFromAlgorand(
/**
* Transfers an asset from Near to a receiver on another chain
* @param client
* @param coreBridge account
* @param coreBridge account
* @param tokenBridge account of the token bridge
* @param assetId account
* @param qty Quantity to transfer
@ -659,7 +753,7 @@ export async function transferTokenFromNear(
let result;
let message_fee = (await client.viewFunction(coreBridge, "message_fee", {}));
let message_fee = await client.viewFunction(coreBridge, "message_fee", {});
if (wormhole) {
result = await client.functionCall({
@ -696,7 +790,9 @@ export async function transferTokenFromNear(
}
if (message_fee > 0) {
let bank = await client.viewFunction(tokenBridge, "bank_balance", { acct: client.accountId });
let bank = await client.viewFunction(tokenBridge, "bank_balance", {
acct: client.accountId,
});
if (!bank[0]) {
await client.functionCall({
@ -744,7 +840,7 @@ export async function transferTokenFromNear(
/**
* Transfers NEAR from Near to a receiver on another chain
* @param client
* @param coreBridge account
* @param coreBridge account
* @param tokenBridge account of the token bridge
* @param qty Quantity to transfer
* @param receiver Receiving account
@ -775,7 +871,7 @@ export async function transferNearFromNear(
payload: payload,
message_fee: message_fee,
},
attachedDeposit: (new BN(qty.toString(10)).add(new BN(message_fee))),
attachedDeposit: new BN(qty.toString(10)).add(new BN(message_fee)),
gas: new BN("100000000000000"),
});

View File

@ -4,6 +4,7 @@ import {
createWrappedOnSolana,
createWrappedOnTerra,
createWrappedOnNear,
submitVAAOnInjective,
} from ".";
import { Bridge__factory } from "../ethers-contracts";
@ -21,6 +22,8 @@ export async function updateWrappedOnEth(
export const updateWrappedOnTerra = createWrappedOnTerra;
export const updateWrappedOnInjective = submitVAAOnInjective;
export const updateWrappedOnSolana = createWrappedOnSolana;
export const updateWrappedOnAlgorand = createWrappedOnAlgorand;

View File

@ -1,3 +1,4 @@
import { expect, test } from "@jest/globals";
import { zeroPad } from "ethers/lib/utils";
import { canonicalAddress } from "../cosmos";
import { tryUint8ArrayToNative, tryNativeToHexString } from "./array";
@ -31,3 +32,11 @@ test("wormchain address conversion", () => {
expect(tryNativeToHexString(human, "wormholechain")).toBe(canonical);
});
test("injective address conversion", () => {
const human = "inj180rl9ezc4389t72pc3vvlkxxs5d9jx60w9eeu3";
const canonical = canonicalAddress(human);
const lpadCanonical = zeroPad(canonical, 32);
const native = tryUint8ArrayToNative(lpadCanonical, "injective");
expect(native).toBe(human);
});

View File

@ -88,6 +88,8 @@ export const tryUint8ArrayToNative = (
}
return humanAddress("terra", a.slice(-20));
}
} else if (chainId === CHAIN_ID_INJECTIVE) {
return humanAddress("inj", a.slice(-20));
} else if (chainId === CHAIN_ID_ALGORAND) {
return uint8ArrayToNativeStringAlgorand(a);
} else if (chainId == CHAIN_ID_WORMHOLE_CHAIN) {
@ -95,8 +97,6 @@ export const tryUint8ArrayToNative = (
return humanAddress("wormhole", a.slice(-20));
} else if (chainId === CHAIN_ID_NEAR) {
throw Error("uint8ArrayToNative: Near not supported yet.");
} else if (chainId === CHAIN_ID_INJECTIVE) {
throw Error("uint8ArrayToNative: Injective not supported yet.");
} else if (chainId === CHAIN_ID_OSMOSIS) {
throw Error("uint8ArrayToNative: Osmosis not supported yet.");
} else if (chainId === CHAIN_ID_SUI) {
@ -207,16 +207,16 @@ export const tryNativeToHexString = (
} else {
return uint8ArrayToHex(zeroPad(canonicalAddress(address), 32));
}
} else if (chainId === CHAIN_ID_TERRA2) {
return buildTokenId(address);
} else if (chainId === CHAIN_ID_TERRA2 || chainId === CHAIN_ID_INJECTIVE) {
return buildTokenId(chainId, address);
} else if (chainId === CHAIN_ID_ALGORAND) {
return nativeStringToHexAlgorand(address);
} else if (chainId == CHAIN_ID_WORMHOLE_CHAIN) {
return uint8ArrayToHex(zeroPad(canonicalAddress(address), 32));
} else if (chainId === CHAIN_ID_NEAR) {
return uint8ArrayToHex(zeroPad(new Uint8Array(Buffer.from(address, "ascii")), 32))
} else if (chainId === CHAIN_ID_INJECTIVE) {
throw Error("hexToNativeString: Injective not supported yet.");
return uint8ArrayToHex(
zeroPad(new Uint8Array(Buffer.from(address, "ascii")), 32)
);
} else if (chainId === CHAIN_ID_OSMOSIS) {
throw Error("hexToNativeString: Osmosis not supported yet.");
} else if (chainId === CHAIN_ID_SUI) {

View File

@ -55,14 +55,13 @@ export type EVMChainName =
| "optimism"
| "gnosis"
| "ropsten";
/**
/**
*
* All the Solana-based chain names that Wormhole supports
*/
export type SolanaChainName =
| "solana"
| "pythnet";
export type SolanaChainName = "solana" | "pythnet";
export type CosmWasmChainName = "terra" | "terra2" | "injective";
export type TerraChainName = "terra" | "terra2";
export type Contracts = {
@ -306,12 +305,12 @@ const TESTNET = {
nft_bridge: undefined,
},
injective: {
core: undefined,
token_bridge: undefined,
core: "inj1xx3aupmgv3ce537c0yce8zzd3sz567syuyedpg",
token_bridge: "inj1q0e70vhrv063eah90mu97sazhywmeegp7myvnh",
nft_bridge: undefined,
},
osmosis: {
core: undefined,
core: "osmo1hggkxr0hpw83f8vuft7ruvmmamsxmwk2hzz6nytdkzyup9krt0dq27sgyx",
token_bridge: undefined,
nft_bridge: undefined,
},
@ -608,8 +607,9 @@ export const CHAIN_ID_TO_NAME: ChainIdToName = Object.entries(CHAINS).reduce(
*/
export type EVMChainId = typeof CHAINS[EVMChainName];
export type TerraChainId = typeof CHAINS[TerraChainName];
export type CosmWasmChainId = typeof CHAINS[CosmWasmChainName];
export type TerraChainId = typeof CHAINS[TerraChainName];
/**
*
* Returns true when called with a valid chain, and narrows the type in the
@ -662,6 +662,22 @@ export function toChainName(chainId: ChainId): ChainName {
return CHAIN_ID_TO_NAME[chainId];
}
export function toCosmWasmChainId(
chainName: CosmWasmChainName
): CosmWasmChainId {
return CHAINS[chainName];
}
export function coalesceCosmWasmChainId(
chain: CosmWasmChainId | CosmWasmChainName
): CosmWasmChainId {
// this is written in a way that for invalid inputs (coming from vanilla
// javascript or someone doing type casting) it will always return undefined.
return typeof chain === "number" && isCosmWasmChain(chain)
? chain
: toCosmWasmChainId(chain);
}
export function coalesceChainId(chain: ChainId | ChainName): ChainId {
// this is written in a way that for invalid inputs (coming from vanilla
// javascript or someone doing type casting) it will always return undefined.
@ -710,6 +726,17 @@ export function isEVMChain(
}
}
export function isCosmWasmChain(
chain: ChainId | ChainName
): chain is CosmWasmChainId | CosmWasmChainName {
const chainId = coalesceChainId(chain);
return (
chainId === CHAIN_ID_TERRA ||
chainId === CHAIN_ID_TERRA2 ||
chainId === CHAIN_ID_INJECTIVE
);
}
export function isTerraChain(
chain: ChainId | ChainName
): chain is TerraChainId | TerraChainName {