sdk/js: Added getIsTransferCompleted
Change-Id: I034595b800ee2b881b9c2a9ab16d6e2a8e4a42e2
This commit is contained in:
parent
0888297b82
commit
70c173af75
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@certusone/wormhole-sdk",
|
"name": "@certusone/wormhole-sdk",
|
||||||
"version": "0.0.8",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@certusone/wormhole-sdk",
|
"name": "@certusone/wormhole-sdk",
|
||||||
"version": "0.0.8",
|
"version": "0.1.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@improbable-eng/grpc-web": "^0.14.0",
|
"@improbable-eng/grpc-web": "^0.14.0",
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
"@solana/web3.js": "^1.24.0",
|
"@solana/web3.js": "^1.24.0",
|
||||||
"@terra-money/terra.js": "^2.0.14",
|
"@terra-money/terra.js": "^2.0.14",
|
||||||
"@terra-money/wallet-provider": "^2.2.0",
|
"@terra-money/wallet-provider": "^2.2.0",
|
||||||
|
"axios": "^0.24.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
"js-base64": "^3.6.1",
|
"js-base64": "^3.6.1",
|
||||||
"protobufjs": "^6.11.2",
|
"protobufjs": "^6.11.2",
|
||||||
|
@ -2370,6 +2371,14 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@terra-money/terra.js/node_modules/axios": {
|
||||||
|
"version": "0.21.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||||
|
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@terra-money/wallet-provider": {
|
"node_modules/@terra-money/wallet-provider": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz",
|
||||||
|
@ -2948,11 +2957,11 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "0.21.1",
|
"version": "0.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.10.0"
|
"follow-redirects": "^1.14.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
|
@ -4201,9 +4210,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.14.2",
|
"version": "1.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||||
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==",
|
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
|
@ -10224,6 +10233,16 @@
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"utf-8-validate": "^5.0.5",
|
"utf-8-validate": "^5.0.5",
|
||||||
"ws": "^7.4.2"
|
"ws": "^7.4.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": {
|
||||||
|
"version": "0.21.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||||
|
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@terra-money/wallet-provider": {
|
"@terra-money/wallet-provider": {
|
||||||
|
@ -10736,11 +10755,11 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.21.1",
|
"version": "0.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.10.0"
|
"follow-redirects": "^1.14.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-jest": {
|
"babel-jest": {
|
||||||
|
@ -11741,9 +11760,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "1.14.2",
|
"version": "1.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||||
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA=="
|
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
|
||||||
},
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
"@solana/web3.js": "^1.24.0",
|
"@solana/web3.js": "^1.24.0",
|
||||||
"@terra-money/terra.js": "^2.0.14",
|
"@terra-money/terra.js": "^2.0.14",
|
||||||
"@terra-money/wallet-provider": "^2.2.0",
|
"@terra-money/wallet-provider": "^2.2.0",
|
||||||
|
"axios": "^0.24.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
"js-base64": "^3.6.1",
|
"js-base64": "^3.6.1",
|
||||||
"protobufjs": "^6.11.2",
|
"protobufjs": "^6.11.2",
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { importCoreWasm } from "../solana/wasm";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { uint8ArrayToHex } from "..";
|
||||||
|
|
||||||
|
export async function getSignedVAAHash(signedVAA: Uint8Array) {
|
||||||
|
const { parse_vaa } = await importCoreWasm();
|
||||||
|
const parsedVAA = parse_vaa(signedVAA);
|
||||||
|
const body = [
|
||||||
|
ethers.utils.defaultAbiCoder.encode(["uint32"], [parsedVAA.timestamp]).substring(2 + (64 - 8)),
|
||||||
|
ethers.utils.defaultAbiCoder.encode(["uint32"], [parsedVAA.nonce]).substring(2 + (64 - 8)),
|
||||||
|
ethers.utils.defaultAbiCoder.encode(["uint16"], [parsedVAA.emitter_chain]).substring(2 + (64 - 4)),
|
||||||
|
ethers.utils.defaultAbiCoder.encode(["bytes32"], [parsedVAA.emitter_address]).substring(2),
|
||||||
|
ethers.utils.defaultAbiCoder.encode(["uint64"], [parsedVAA.sequence]).substring(2 + (64 - 16)),
|
||||||
|
ethers.utils.defaultAbiCoder.encode(["uint8"], [parsedVAA.consistency_level]).substring(2 + (64 - 2)),
|
||||||
|
uint8ArrayToHex(parsedVAA.payload),
|
||||||
|
];
|
||||||
|
return ethers.utils.solidityKeccak256(["bytes"], [ethers.utils.solidityKeccak256(["bytes"], ["0x" + body.join("")])]);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./getClaimAddress";
|
export * from "./getClaimAddress";
|
||||||
export * from "./getEmitterAddress";
|
export * from "./getEmitterAddress";
|
||||||
|
export * from "./getSignedVAAHash";
|
||||||
export * from "./parseSequenceFromLog";
|
export * from "./parseSequenceFromLog";
|
||||||
|
|
|
@ -20,6 +20,15 @@ export const SOLANA_CORE_BRIDGE_ADDRESS =
|
||||||
"Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
|
"Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
|
||||||
export const SOLANA_TOKEN_BRIDGE_ADDRESS =
|
export const SOLANA_TOKEN_BRIDGE_ADDRESS =
|
||||||
"B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE";
|
"B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE";
|
||||||
|
export const TERRA_NODE_URL = "http://localhost:1317";
|
||||||
|
export const TERRA_CHAIN_ID = "localterra";
|
||||||
|
export const TERRA_GAS_PRICES_URL = "http://localhost:3060/v1/txs/gas_prices";
|
||||||
|
export const TERRA_CORE_BRIDGE_ADDRESS =
|
||||||
|
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
||||||
|
export const TERRA_TOKEN_BRIDGE_ADDRESS =
|
||||||
|
"terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4";
|
||||||
|
export const TERRA_PRIVATE_KEY =
|
||||||
|
"notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius";
|
||||||
export const TEST_ERC20 = "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A";
|
export const TEST_ERC20 = "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A";
|
||||||
export const TEST_SOLANA_TOKEN = "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ";
|
export const TEST_SOLANA_TOKEN = "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ";
|
||||||
export const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
|
export const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { parseUnits } from "@ethersproject/units";
|
import { parseUnits } from "@ethersproject/units";
|
||||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||||
import { describe, jest, test } from "@jest/globals";
|
import { describe, expect, jest, test } from "@jest/globals";
|
||||||
import {
|
import {
|
||||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
Token,
|
Token,
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
} from "@solana/spl-token";
|
} from "@solana/spl-token";
|
||||||
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
||||||
|
import { LCDClient, MnemonicKey } from "@terra-money/terra.js";
|
||||||
|
import axios from "axios";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import {
|
import {
|
||||||
approveEth,
|
approveEth,
|
||||||
|
@ -14,11 +16,16 @@ import {
|
||||||
attestFromSolana,
|
attestFromSolana,
|
||||||
CHAIN_ID_ETH,
|
CHAIN_ID_ETH,
|
||||||
CHAIN_ID_SOLANA,
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
createWrappedOnEth,
|
createWrappedOnEth,
|
||||||
createWrappedOnSolana,
|
createWrappedOnSolana,
|
||||||
|
createWrappedOnTerra,
|
||||||
getEmitterAddressEth,
|
getEmitterAddressEth,
|
||||||
getEmitterAddressSolana,
|
getEmitterAddressSolana,
|
||||||
getForeignAssetSolana,
|
getForeignAssetSolana,
|
||||||
|
getIsTransferCompletedEth,
|
||||||
|
getIsTransferCompletedSolana,
|
||||||
|
getIsTransferCompletedTerra,
|
||||||
hexToUint8Array,
|
hexToUint8Array,
|
||||||
nativeToHexString,
|
nativeToHexString,
|
||||||
parseSequenceFromLogEth,
|
parseSequenceFromLogEth,
|
||||||
|
@ -26,6 +33,7 @@ import {
|
||||||
postVaaSolana,
|
postVaaSolana,
|
||||||
redeemOnEth,
|
redeemOnEth,
|
||||||
redeemOnSolana,
|
redeemOnSolana,
|
||||||
|
redeemOnTerra,
|
||||||
transferFromEth,
|
transferFromEth,
|
||||||
transferFromSolana,
|
transferFromSolana,
|
||||||
} from "../..";
|
} from "../..";
|
||||||
|
@ -40,6 +48,11 @@ import {
|
||||||
SOLANA_HOST,
|
SOLANA_HOST,
|
||||||
SOLANA_PRIVATE_KEY,
|
SOLANA_PRIVATE_KEY,
|
||||||
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
TERRA_CHAIN_ID,
|
||||||
|
TERRA_GAS_PRICES_URL,
|
||||||
|
TERRA_NODE_URL,
|
||||||
|
TERRA_PRIVATE_KEY,
|
||||||
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||||
TEST_ERC20,
|
TEST_ERC20,
|
||||||
TEST_SOLANA_TOKEN,
|
TEST_SOLANA_TOKEN,
|
||||||
WORMHOLE_RPC_HOSTS,
|
WORMHOLE_RPC_HOSTS,
|
||||||
|
@ -51,7 +64,6 @@ jest.setTimeout(60000);
|
||||||
|
|
||||||
// TODO: setup keypair and provider/signer before, destroy provider after
|
// TODO: setup keypair and provider/signer before, destroy provider after
|
||||||
// TODO: make the repeatable (can't attest an already attested token)
|
// TODO: make the repeatable (can't attest an already attested token)
|
||||||
// TODO: add Terra
|
|
||||||
|
|
||||||
describe("Integration Tests", () => {
|
describe("Integration Tests", () => {
|
||||||
describe("Ethereum to Solana", () => {
|
describe("Ethereum to Solana", () => {
|
||||||
|
@ -223,6 +235,13 @@ describe("Integration Tests", () => {
|
||||||
payerAddress,
|
payerAddress,
|
||||||
Buffer.from(signedVAA)
|
Buffer.from(signedVAA)
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
await getIsTransferCompletedSolana(
|
||||||
|
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
signedVAA,
|
||||||
|
connection
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
// redeem tokens on solana
|
// redeem tokens on solana
|
||||||
const transaction = await redeemOnSolana(
|
const transaction = await redeemOnSolana(
|
||||||
connection,
|
connection,
|
||||||
|
@ -237,6 +256,13 @@ describe("Integration Tests", () => {
|
||||||
transaction.serialize()
|
transaction.serialize()
|
||||||
);
|
);
|
||||||
await connection.confirmTransaction(txid);
|
await connection.confirmTransaction(txid);
|
||||||
|
expect(
|
||||||
|
await getIsTransferCompletedSolana(
|
||||||
|
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
signedVAA,
|
||||||
|
connection
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
provider.destroy();
|
provider.destroy();
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -377,7 +403,21 @@ describe("Integration Tests", () => {
|
||||||
transport: NodeHttpTransport(),
|
transport: NodeHttpTransport(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
await getIsTransferCompletedEth(
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
provider,
|
||||||
|
signedVAA
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
||||||
|
expect(
|
||||||
|
await getIsTransferCompletedEth(
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
provider,
|
||||||
|
signedVAA
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
provider.destroy();
|
provider.destroy();
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -390,4 +430,178 @@ describe("Integration Tests", () => {
|
||||||
});
|
});
|
||||||
// TODO: it has increased balance
|
// TODO: it has increased balance
|
||||||
});
|
});
|
||||||
|
describe("Ethereum to Terra", () => {
|
||||||
|
test("Attest Ethereum ERC-20 to Terra", (done) => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
// create a signer for Eth
|
||||||
|
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||||
|
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||||
|
// attest the test token
|
||||||
|
const receipt = await attestFromEth(
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
signer,
|
||||||
|
TEST_ERC20
|
||||||
|
);
|
||||||
|
// get the sequence from the logs (needed to fetch the vaa)
|
||||||
|
const sequence = parseSequenceFromLogEth(
|
||||||
|
receipt,
|
||||||
|
ETH_CORE_BRIDGE_ADDRESS
|
||||||
|
);
|
||||||
|
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||||
|
// poll until the guardian(s) witness and sign the vaa
|
||||||
|
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||||
|
WORMHOLE_RPC_HOSTS,
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
emitterAddress,
|
||||||
|
sequence,
|
||||||
|
{
|
||||||
|
transport: NodeHttpTransport(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const lcd = new LCDClient({
|
||||||
|
URL: TERRA_NODE_URL,
|
||||||
|
chainID: TERRA_CHAIN_ID,
|
||||||
|
});
|
||||||
|
const mk = new MnemonicKey({
|
||||||
|
mnemonic: TERRA_PRIVATE_KEY,
|
||||||
|
});
|
||||||
|
const wallet = lcd.wallet(mk);
|
||||||
|
const msg = await createWrappedOnTerra(
|
||||||
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
wallet.key.accAddress,
|
||||||
|
signedVAA
|
||||||
|
);
|
||||||
|
const gasPrices = await axios
|
||||||
|
.get(TERRA_GAS_PRICES_URL)
|
||||||
|
.then((result) => result.data);
|
||||||
|
const feeEstimate = await lcd.tx.estimateFee(
|
||||||
|
wallet.key.accAddress,
|
||||||
|
[msg],
|
||||||
|
{
|
||||||
|
feeDenoms: ["uluna"],
|
||||||
|
gasPrices,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const tx = await wallet.createAndSignTx({
|
||||||
|
msgs: [msg],
|
||||||
|
memo: "test",
|
||||||
|
feeDenoms: ["uluna"],
|
||||||
|
gasPrices,
|
||||||
|
fee: feeEstimate,
|
||||||
|
});
|
||||||
|
await lcd.tx.broadcast(tx);
|
||||||
|
provider.destroy();
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
done(
|
||||||
|
"An error occurred while trying to attest from Ethereum to Terra"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
// TODO: it is attested
|
||||||
|
test("Send Ethereum ERC-20 to Terra", (done) => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
// create a signer for Eth
|
||||||
|
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||||
|
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||||
|
const amount = parseUnits("1", 18);
|
||||||
|
// approve the bridge to spend tokens
|
||||||
|
await approveEth(
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
TEST_ERC20,
|
||||||
|
signer,
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
const lcd = new LCDClient({
|
||||||
|
URL: TERRA_NODE_URL,
|
||||||
|
chainID: TERRA_CHAIN_ID,
|
||||||
|
});
|
||||||
|
const mk = new MnemonicKey({
|
||||||
|
mnemonic: TERRA_PRIVATE_KEY,
|
||||||
|
});
|
||||||
|
const wallet = lcd.wallet(mk);
|
||||||
|
// transfer tokens
|
||||||
|
const receipt = await transferFromEth(
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
signer,
|
||||||
|
TEST_ERC20,
|
||||||
|
amount,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
hexToUint8Array(
|
||||||
|
nativeToHexString(wallet.key.accAddress, CHAIN_ID_TERRA) || ""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// get the sequence from the logs (needed to fetch the vaa)
|
||||||
|
const sequence = parseSequenceFromLogEth(
|
||||||
|
receipt,
|
||||||
|
ETH_CORE_BRIDGE_ADDRESS
|
||||||
|
);
|
||||||
|
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||||
|
// poll until the guardian(s) witness and sign the vaa
|
||||||
|
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||||
|
WORMHOLE_RPC_HOSTS,
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
emitterAddress,
|
||||||
|
sequence,
|
||||||
|
{
|
||||||
|
transport: NodeHttpTransport(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await getIsTransferCompletedTerra(
|
||||||
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
signedVAA,
|
||||||
|
wallet.key.accAddress,
|
||||||
|
lcd,
|
||||||
|
TERRA_GAS_PRICES_URL
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
|
const msg = await redeemOnTerra(
|
||||||
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
wallet.key.accAddress,
|
||||||
|
signedVAA
|
||||||
|
);
|
||||||
|
const gasPrices = await axios
|
||||||
|
.get(TERRA_GAS_PRICES_URL)
|
||||||
|
.then((result) => result.data);
|
||||||
|
const feeEstimate = await lcd.tx.estimateFee(
|
||||||
|
wallet.key.accAddress,
|
||||||
|
[msg],
|
||||||
|
{
|
||||||
|
memo: "localhost",
|
||||||
|
feeDenoms: ["uluna"],
|
||||||
|
gasPrices,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const tx = await wallet.createAndSignTx({
|
||||||
|
msgs: [msg],
|
||||||
|
memo: "localhost",
|
||||||
|
feeDenoms: ["uluna"],
|
||||||
|
gasPrices,
|
||||||
|
fee: feeEstimate,
|
||||||
|
});
|
||||||
|
await lcd.tx.broadcast(tx);
|
||||||
|
expect(
|
||||||
|
await getIsTransferCompletedTerra(
|
||||||
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
signedVAA,
|
||||||
|
wallet.key.accAddress,
|
||||||
|
lcd,
|
||||||
|
TERRA_GAS_PRICES_URL
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
|
provider.destroy();
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
done("An error occurred while trying to send from Ethereum to Terra");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
// TODO: it has increased balance
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { Bridge__factory } from "../ethers-contracts";
|
||||||
|
import { getSignedVAAHash } from "../bridge";
|
||||||
|
import { importCoreWasm } from "../solana/wasm";
|
||||||
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
|
import { LCDClient } from "@terra-money/terra.js";
|
||||||
|
import axios from "axios";
|
||||||
|
import { redeemOnTerra } from ".";
|
||||||
|
|
||||||
|
export async function getIsTransferCompletedEth(
|
||||||
|
tokenBridgeAddress: string,
|
||||||
|
provider: ethers.providers.Provider,
|
||||||
|
signedVAA: Uint8Array
|
||||||
|
) {
|
||||||
|
const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
|
||||||
|
const signedVAAHash = await getSignedVAAHash(signedVAA);
|
||||||
|
return await tokenBridge.isTransferCompleted(signedVAAHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getIsTransferCompletedTerra(
|
||||||
|
tokenBridgeAddress: string,
|
||||||
|
signedVAA: Uint8Array,
|
||||||
|
walletAddress: string,
|
||||||
|
client: LCDClient,
|
||||||
|
gasPriceUrl: string
|
||||||
|
) {
|
||||||
|
const msg = await redeemOnTerra(tokenBridgeAddress, walletAddress, signedVAA);
|
||||||
|
// TODO: remove gasPriceUrl and just use the client's gas prices
|
||||||
|
const gasPrices = await axios.get(gasPriceUrl).then((result) => result.data);
|
||||||
|
try {
|
||||||
|
await client.tx.estimateFee(walletAddress, [msg], {
|
||||||
|
memo: "already redeemed calculation",
|
||||||
|
feeDenoms: ["uluna"],
|
||||||
|
gasPrices,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// redeemed if the VAA was already executed
|
||||||
|
return e.response.data.error.includes("VaaAlreadyExecuted");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getIsTransferCompletedSolana(
|
||||||
|
tokenBridgeAddress: string,
|
||||||
|
signedVAA: Uint8Array,
|
||||||
|
connection: Connection
|
||||||
|
) {
|
||||||
|
const { claim_address } = await importCoreWasm();
|
||||||
|
const claimAddress = await claim_address(tokenBridgeAddress, signedVAA);
|
||||||
|
const claimInfo = await connection.getAccountInfo(
|
||||||
|
new PublicKey(claimAddress),
|
||||||
|
"confirmed"
|
||||||
|
);
|
||||||
|
return !!claimInfo;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
export * from "./attest";
|
export * from "./attest";
|
||||||
export * from "./createWrapped";
|
export * from "./createWrapped";
|
||||||
export * from "./getForeignAsset";
|
export * from "./getForeignAsset";
|
||||||
|
export * from "./getIsTransferCompleted";
|
||||||
export * from "./getIsWrappedAsset";
|
export * from "./getIsWrappedAsset";
|
||||||
export * from "./getOriginalAsset";
|
export * from "./getOriginalAsset";
|
||||||
export * from "./redeem";
|
export * from "./redeem";
|
||||||
|
|
Loading…
Reference in New Issue