| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- import { fromUint8Array } from "js-base64";
- import {
- LCDClient,
- LCDClientConfig,
- MnemonicKey,
- MsgExecuteContract,
- } from "@terra-money/terra.js";
- import { hexToUint8Array } from "@certusone/wormhole-sdk";
- import axios from "axios";
- import { logger } from "../helpers";
- import { Relay, RelayResult, RelayRetcode, PriceId } from "./iface";
- export const TERRA_GAS_PRICES_URL = "https://fcd.terra.dev/v1/txs/gas_prices";
- export class TerraRelay implements Relay {
- readonly nodeUrl: string;
- readonly terraChainId: string;
- readonly walletPrivateKey: string;
- readonly coin: string;
- readonly contractAddress: string;
- readonly lcdConfig: LCDClientConfig;
- constructor(cfg: {
- nodeUrl: string;
- terraChainId: string;
- walletPrivateKey: string;
- coin: string;
- contractAddress: string;
- }) {
- this.nodeUrl = cfg.nodeUrl;
- this.terraChainId = cfg.terraChainId;
- this.walletPrivateKey = cfg.walletPrivateKey;
- this.coin = cfg.coin;
- this.contractAddress = cfg.contractAddress;
- this.lcdConfig = {
- URL: this.nodeUrl,
- chainID: this.terraChainId,
- };
- logger.info(
- "Terra connection parameters: url: [" +
- this.nodeUrl +
- "], terraChainId: [" +
- this.terraChainId +
- "], coin: [" +
- this.coin +
- "], contractAddress: [" +
- this.contractAddress +
- "]"
- );
- }
- async relay(signedVAAs: Array<string>) {
- let terraRes;
- try {
- logger.debug("relaying " + signedVAAs.length + " messages to terra");
- logger.debug("TIME: connecting to terra");
- const lcdClient = new LCDClient(this.lcdConfig);
- const mk = new MnemonicKey({
- mnemonic: this.walletPrivateKey,
- });
- const wallet = lcdClient.wallet(mk);
- logger.debug("TIME: creating messages");
- let msgs = new Array<MsgExecuteContract>();
- for (let idx = 0; idx < signedVAAs.length; ++idx) {
- const msg = new MsgExecuteContract(
- wallet.key.accAddress,
- this.contractAddress,
- {
- update_price_feeds: {
- data: Buffer.from(signedVAAs[idx], "hex").toString("base64"),
- },
- }
- );
- msgs.push(msg);
- }
- let gasPrices;
- try {
- gasPrices = await axios
- .get(TERRA_GAS_PRICES_URL)
- .then((result) => result.data);
- } catch (e: any) {
- logger.warn(e);
- logger.warn(e.stack);
- logger.warn(
- "Couldn't fetch gas price and fee estimate. Using default values"
- );
- }
- const tx = await wallet.createAndSignTx({
- msgs: msgs,
- memo: "P2T",
- feeDenoms: [this.coin],
- gasPrices,
- });
- logger.debug("TIME: sending msg");
- terraRes = await lcdClient.tx.broadcastSync(tx);
- logger.debug(
- `TIME:submitted to terra: terraRes: ${JSON.stringify(terraRes)}`
- );
- // Act on known Terra errors
- if (terraRes.raw_log) {
- if (terraRes.raw_log.search("VaaAlreadyExecuted") >= 0) {
- logger.error(
- "Already Executed:",
- terraRes.txhash
- ? terraRes.txhash
- : "<INTERNAL: no txhash for AlreadyExecuted>"
- );
- return new RelayResult(RelayRetcode.AlreadyExecuted, []);
- } else if (terraRes.raw_log.search("insufficient funds") >= 0) {
- logger.error(
- "relay failed due to insufficient funds: ",
- JSON.stringify(terraRes)
- );
- return new RelayResult(RelayRetcode.InsufficientFunds, []);
- } else if (terraRes.raw_log.search("failed") >= 0) {
- logger.error(
- "relay seems to have failed: ",
- JSON.stringify(terraRes)
- );
- return new RelayResult(RelayRetcode.Fail, []);
- }
- } else {
- logger.warn("No logs were found, result: ", JSON.stringify(terraRes));
- }
- // Base case, no errors were detected and no exceptions were thrown
- if (terraRes.txhash) {
- return new RelayResult(RelayRetcode.Success, [terraRes.txhash]);
- }
- } catch (e: any) {
- // Act on known Terra exceptions
- logger.error(e);
- logger.error(e.stack);
- if (
- e.message &&
- e.message.search("timeout") >= 0 &&
- e.message.search("exceeded") >= 0
- ) {
- logger.error("relay timed out: %o", e);
- return new RelayResult(RelayRetcode.Timeout, []);
- } else if (
- e.response?.data?.error &&
- e.response.data.error.search("VaaAlreadyExecuted") >= 0
- ) {
- logger.error("VAA Already Executed");
- logger.error(e.response.data.error);
- return new RelayResult(RelayRetcode.AlreadyExecuted, []);
- } else if (
- e.response?.data?.message &&
- e.response.data.message.search("account sequence mismatch") >= 0
- ) {
- logger.error("Account sequence mismatch");
- logger.error(e.response.data.message);
- return new RelayResult(RelayRetcode.SeqNumMismatch, []);
- } else {
- logger.error("Unknown error:");
- logger.error(e.toString());
- return new RelayResult(RelayRetcode.Fail, []);
- }
- }
- logger.error("INTERNAL: Terra relay() logic failed to produce a result");
- return new RelayResult(RelayRetcode.Fail, []);
- }
- async query(priceId: PriceId) {
- logger.info("Querying terra for price info for priceId [" + priceId + "]");
- const lcdClient = new LCDClient(this.lcdConfig);
- const mk = new MnemonicKey({
- mnemonic: this.walletPrivateKey,
- });
- return await lcdClient.wasm.contractQuery(this.contractAddress, {
- price_feed: {
- id: priceId,
- },
- });
- }
- async getPayerInfo(): Promise<{ address: string; balance: bigint }> {
- const lcdClient = new LCDClient(this.lcdConfig);
- const mk = new MnemonicKey({
- mnemonic: this.walletPrivateKey,
- });
- const wallet = lcdClient.wallet(mk);
- let balance: number = NaN;
- try {
- logger.debug("querying wallet balance");
- let coins: any;
- let pagnation: any;
- [coins, pagnation] = await lcdClient.bank.balance(wallet.key.accAddress);
- logger.debug("wallet query returned: %o", coins);
- if (coins) {
- let coin = coins.get(this.coin);
- if (coin) {
- balance = parseInt(coin.toData().amount);
- } else {
- logger.error(
- "failed to query coin balance, coin [" +
- this.coin +
- "] is not in the wallet, coins: %o",
- coins
- );
- }
- } else {
- logger.error("failed to query coin balance!");
- }
- } catch (e) {
- logger.error("failed to query coin balance: %o", e);
- }
- return { address: wallet.key.accAddress, balance: BigInt(balance) };
- }
- }
|