@dojima-wallet/connection
Version:
Initialise and connection for layer 1&2 blockchain
878 lines (797 loc) • 23.3 kB
text/typescript
import { cosmosclient, proto, rest } from "@cosmos-client/core";
import {
Balance,
FeeType,
Fees,
Network,
TxHash,
TxType,
singleFee,
} from "../client";
import { CosmosSDKClient, TxLog } from "../cosmos";
import {
Address,
Asset,
AssetDOJNative,
BaseAmount,
assetAmount,
assetFromString,
assetToBase,
assetToString,
baseAmount,
isAssetDOJNative,
isSynthAsset,
} from "@dojima-wallet/utils";
import axios from "axios";
import * as bech32Buffer from "bech32-buffer";
import Long from "long";
import {
MsgCreateOperator,
MsgRegisterChain,
MsgNativeTx,
MsgSetIpAddressTx,
MsgSetPubkeysTx,
MsgSetVersionTx,
MsgCreateEndpoint,
} from "./messages";
import { hermes } from "./proto/MsgCompiled";
import { ChainId, ExplorerUrls, NodeInfoResponse, TxData } from "./types";
export const DOJ_DECIMAL = 8;
export const DEFAULT_GAS_ADJUSTMENT = 2;
export const DEFAULT_GAS_LIMIT_VALUE = "8000000";
export const DEPOSIT_GAS_LIMIT_VALUE = "600000000";
export const MAX_TX_COUNT = 100;
const DENOM_DOJ_NATIVE = "doj";
// const DEFAULT_MAINNET_EXPLORER_URL = "https://api-h4s.dojima.network";
// const DEFAULT_STAGENET_EXPLORER_URL = "https://api-h4s.dojima.network";
// const DEFAULT_TESTNET_EXPLORER_URL = "https://api-test-h4s.dojima.network";
// // const DEFAULT_TESTNET_EXPLORER_URL = "http://localhost:1317";
// // const txUrl = `${DEFAULT_EXPLORER_URL}/tx`;
// // const addressUrl = `${DEFAULT_EXPLORER_URL}/address`;
// export const defaultExplorerUrls: ExplorerUrls = {
// root: {
// [Network.Testnet]: `${DEFAULT_TESTNET_EXPLORER_URL}?network=testnet`,
// [Network.Stagenet]: `${DEFAULT_STAGENET_EXPLORER_URL}?network=stagenet`,
// [Network.Mainnet]: DEFAULT_MAINNET_EXPLORER_URL,
// },
// tx: {
// [Network.Testnet]: `${DEFAULT_TESTNET_EXPLORER_URL}/tx`,
// [Network.Stagenet]: `${DEFAULT_STAGENET_EXPLORER_URL}/tx`,
// [Network.Mainnet]: `${DEFAULT_MAINNET_EXPLORER_URL}/tx`,
// },
// address: {
// [Network.Testnet]: `${DEFAULT_TESTNET_EXPLORER_URL}/address`,
// [Network.Stagenet]: `${DEFAULT_STAGENET_EXPLORER_URL}/address`,
// [Network.Mainnet]: `${DEFAULT_MAINNET_EXPLORER_URL}/address`,
// },
// };
/**
* Get denomination from Asset
*
* @param {Asset} asset
* @returns {string} The denomination of the given asset.
*/
export const getDenom = (asset: Asset): string => {
if (isAssetDOJNative(asset)) return DENOM_DOJ_NATIVE;
if (isSynthAsset(asset)) return assetToString(asset).toLowerCase();
return asset.symbol.toLowerCase();
};
/**
* Get Asset from denomination
*
* @param {string} denom
* @returns {Asset|null} The asset of the given denomination.
*/
export const assetFromDenom = (denom: string): Asset | null => {
if (denom === DENOM_DOJ_NATIVE) return AssetDOJNative;
return assetFromString(denom.toUpperCase());
};
/**
* Response guard for transaction broadcast
*
* @param {any} response The response from the node.
* @returns {boolean} `true` or `false`.
*/
export const isBroadcastSuccess = (response: unknown): boolean =>
typeof response === "object" &&
response !== null &&
"logs" in response &&
(response as Record<string, unknown>).logs !== undefined;
/**
* Get address prefix based on the network.
*
* @param {Network} network
* @returns {string} The address prefix based on the network.
*
**/
export const getPrefix = (network: Network) => {
switch (network) {
case Network.Mainnet:
return "dojima";
case Network.Stagenet:
return "sdojima";
// case Network.Testnet:
// return "dojima";
case Network.Testnet:
return "tdojima";
}
};
/**
* Register type for encoding `MsgSetVersion` messages
*/
export const registerSetVersionCodecs = () => {
cosmosclient.codec.register(
"/hermes.hermes.v1beta1.types.MsgSetVersion",
hermes.hermes.v1beta1.types.MsgSetVersion
);
};
/**
* Register type for encoding `MsgSetNodeKeys` messages
*/
export const registerSetNodePubkeysCodecs = () => {
cosmosclient.codec.register(
"/hermes.hermes.v1beta1.types.MsgSetNodeKeys",
hermes.hermes.v1beta1.types.MsgSetNodeKeys
);
};
/**
* Register type for encoding `MsgCreateOperator` messages
*/
export const registerCreateOperatorCodecs = () => {
cosmosclient.codec.register(
"/hermes.operatorstaking.v1beta1.MsgCreateOperator",
hermes.operatorstaking.v1beta1.MsgCreateOperator
);
};
/**
* Register type for encoding `MsgRegisterChain` messages
*/
export const registerRegisterChainCodecs = () => {
cosmosclient.codec.register(
"/hermes.chainlist.v1beta1.MsgRegisterChainWithCU",
hermes.chainlist.v1beta1.MsgRegisterChainWithCU
);
};
export const registerCreateEndpointCodecs = () => {
cosmosclient.codec.register(
"/hermes.chainlist.v1beta1.MsgCreateEndpoint",
hermes.chainlist.v1beta1.MsgCreateEndpoint
);
};
/**
* Register type for encoding `MsgDeposit` messages
*/
export const registerDepositCodecs = () => {
cosmosclient.codec.register(
"/hermes.hermes.v1beta1.types.MsgDeposit",
hermes.hermes.v1beta1.types.MsgDeposit
);
};
/**
* Register type for encoding `MsgSend` messages
*/
export const registerSendCodecs = () => {
cosmosclient.codec.register(
"/hermes.hermes.v1beta1.types.MsgSend",
hermes.hermes.v1beta1.types.MsgSend
);
};
/**
* Register type for encoding `MsgSetIpAddress` messages
*/
export const registerSetIpAddrCodecs = () => {
cosmosclient.codec.register(
"/hermes.hermes.v1beta1.types.MsgSetIPAddress",
hermes.hermes.v1beta1.types.MsgSetIPAddress
);
};
/**
* Parse transaction data from event logs
*
* @param {TxLog[]} logs List of tx logs
* @param {Address} address - Address to get transaction data for
* @returns {TxData} Parsed transaction data
*/
export const getDepositTxDataFromLogs = (
logs: TxLog[],
address: Address
): TxData => {
const events = logs[0]?.events;
if (!events) {
throw Error("No events in logs available");
}
type TransferData = { sender: string; recipient: string; amount: BaseAmount };
type TransferDataList = TransferData[];
const transferDataList: TransferDataList = events.reduce(
(acc: TransferDataList, { type, attributes }) => {
if (type === "transfer") {
return attributes.reduce((acc2, { key, value }, index) => {
if (index % 3 === 0)
acc2.push({
sender: "",
recipient: "",
amount: baseAmount(0, DOJ_DECIMAL),
});
const newData = acc2[acc2.length - 1];
if (key === "sender") newData.sender = value;
if (key === "recipient") newData.recipient = value;
if (key === "amount")
newData.amount = baseAmount(value.replace(/doj/, ""), DOJ_DECIMAL);
return acc2;
}, acc);
}
return acc;
},
[]
);
const txData: TxData = transferDataList
// filter out txs which are not based on given address
.filter(
({ sender, recipient }) => sender === address || recipient === address
)
// transform `TransferData` -> `TxData`
.reduce(
(acc: TxData, { sender, recipient, amount }) => ({
...acc,
from: [...acc.from, { amount, from: sender }],
to: [...acc.to, { amount, to: recipient }],
}),
{ from: [], to: [], type: TxType.Transfer }
);
return txData;
};
/**
* Get the default fee.
*
* @returns {Fees} The default fee.
*/
export const getDefaultFees = (): Fees => {
const fee = assetToBase(assetAmount(0.02 /* 0.02 DOJ */, DOJ_DECIMAL));
return singleFee(FeeType.FlatFee, fee);
};
/**
* Get transaction type.
*
* @param {string} txData the transaction input data
* @param {string} encoding `base64` or `hex`
* @returns {string} the transaction type.
*/
export const getTxType = (
txData: string,
encoding: "base64" | "hex"
): string => {
return Buffer.from(txData, encoding).toString().slice(4);
};
/**
* Helper to get HermesChain's chain id
* @param {string} nodeUrl HermesNode url
*/
export const getChainId = async (nodeUrl: string): Promise<ChainId> => {
const { data } = await axios.get<NodeInfoResponse>(
`${nodeUrl}/cosmos/base/tendermint/v1beta1/node_info`
);
return (
data?.default_node_info?.network ||
Promise.reject("Could not parse chain id")
);
};
/**
* Builds final unsigned TX
*
* @param cosmosSdk - CosmosSDK
* @param txBody - txBody with encoded Msgs
* @param signerPubkey - signerPubkey string
* @param sequence - account sequence
* @param gasLimit - transaction gas limit
* @returns
*/
export const buildUnsignedTx = ({
cosmosSdk,
txBody,
signerPubkey,
sequence,
gasLimit,
}: {
cosmosSdk: cosmosclient.CosmosSDK;
txBody: proto.cosmos.tx.v1beta1.TxBody;
signerPubkey: proto.google.protobuf.Any;
sequence: Long;
gasLimit?: Long;
}): cosmosclient.TxBuilder => {
const authInfo = new proto.cosmos.tx.v1beta1.AuthInfo({
signer_infos: [
{
public_key: signerPubkey,
mode_info: {
single: {
mode: proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
},
},
sequence: sequence,
},
],
fee: {
amount: null,
gas_limit: gasLimit || null,
},
});
return new cosmosclient.TxBuilder(cosmosSdk, txBody, authInfo);
};
/**
* Estimates usage of gas
*
* Note: Be careful by using this helper function,
* it's still experimental and result might be incorrect.
* Change `multiplier` to get a valid estimation of gas.
*/
export const getEstimatedGas = async ({
cosmosSDKClient,
txBody,
privKey,
accountNumber,
accountSequence,
multiplier,
}: {
cosmosSDKClient: CosmosSDKClient;
txBody: proto.cosmos.tx.v1beta1.TxBody;
privKey: proto.cosmos.crypto.secp256k1.PrivKey;
accountNumber: Long;
accountSequence: Long;
multiplier?: number;
}): Promise<Long | undefined> => {
const pubKey = privKey.pubKey();
const txBuilder = buildUnsignedTx({
cosmosSdk: cosmosSDKClient.sdk,
txBody: txBody,
signerPubkey: cosmosclient.codec.instanceToProtoAny(pubKey),
sequence: accountSequence,
});
const signDocBytes = txBuilder.signDocBytes(accountNumber);
txBuilder.addSignature(privKey.sign(signDocBytes));
const resp = await rest.tx.simulate(cosmosSDKClient.sdk, {
tx_bytes: txBuilder.txBytes(),
});
const estimatedGas = resp.data?.gas_info?.gas_used ?? null;
if (!estimatedGas) {
throw new Error("Could not get data of estimated gas");
}
return Long.fromString(estimatedGas).multiply(
multiplier || DEFAULT_GAS_ADJUSTMENT
);
};
export const buildRegisterChainTx = async ({
msgRegisterChain,
nodeUrl,
chainId,
}: {
msgRegisterChain: MsgRegisterChain;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const { chain, computeUnits, signer } = msgRegisterChain;
const signerAddr = signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
if (!chain.chainId) {
throw new Error("Chain id is not provided");
}
const registerChain = {
Chain: {
Name: chain.name.toString(),
Ticker: chain.ticker,
Id: chain.chainId || "",
},
Cu: {
blockunits: computeUnits.blockUnits,
transactionunits: computeUnits.txnUnits,
},
signer: signerDecoded.data,
};
const registerChainMsg =
hermes.chainlist.v1beta1.MsgRegisterChainWithCU.fromObject(registerChain);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(registerChainMsg)],
});
};
export const buildCreateEndpointTx = async ({
msgCreateEndpoint,
nodeUrl,
chainId,
}: {
msgCreateEndpoint: MsgCreateEndpoint;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const { chain, rpcUrl, wsUrl, signer } = msgCreateEndpoint;
const signerAddr = signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
if (!chain.chainId) {
throw new Error("Chain id is not provided");
}
const createEndpoint = {
Chain: {
Name: chain.name,
Ticker: chain.ticker,
Id: chain.chainId || "",
},
Rpc: rpcUrl,
Ws: wsUrl,
signer: signerDecoded.data,
};
const createEndpointMsg =
hermes.chainlist.v1beta1.MsgCreateEndpoint.fromObject(createEndpoint);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(createEndpointMsg)],
});
};
/**
* Builds a create operator transaction
* @param {MsgCreateOperator} msgCreateOperator
* @param {string} nodeUrl
* @param {ChainId} chainId
*/
export const buildCreateOperatorTx = async ({
msgCreateOperator,
nodeUrl,
chainId,
}: {
msgCreateOperator: MsgCreateOperator;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const { signer, stakeAmount, serverAddress } = msgCreateOperator;
const signerAddr = signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
const createOperatorMsg = {
stake: stakeAmount,
server: serverAddress,
signer: signerDecoded.data,
};
const operatorMsg =
hermes.operatorstaking.v1beta1.MsgCreateOperator.fromObject(
createOperatorMsg
);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(operatorMsg)],
});
};
/**
* Structure a MsgDeposit
*
* @param {MsgNativeTx} msgNativeTx Msg of type `MsgNativeTx`.
* @param {string} nodeUrl Node url
* @param {chainId} ChainId Chain id of the network
*
* @returns {Tx} The transaction details of the given transaction id.
*
* @throws {"Invalid client url"} Thrown if the client url is an invalid one.
*/
export const buildDepositTx = async ({
msgNativeTx,
nodeUrl,
chainId,
}: {
msgNativeTx: MsgNativeTx;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const signerAddr = msgNativeTx.signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
const msgDepositObj = {
coins: msgNativeTx.coins,
memo: msgNativeTx.memo,
signer: signerDecoded.data,
};
const depositMsg =
hermes.hermes.v1beta1.types.MsgDeposit.fromObject(msgDepositObj);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(depositMsg)],
memo: msgNativeTx.memo,
});
};
/**
* Structure a MsgSetVersion
*
* @param {MsgSetVersionTx} msgSetVersionTx Msg of type `MsgSetVersionTx`.
* @param {string} nodeUrl Node url
* @param {chainId} ChainId Chain id of the network
*
* @returns {Tx} The transaction details of the given transaction id.
*
* @throws {"Invalid client url"} Thrown if the client url is an invalid one.
*/
export const buildSetVersionTx = async ({
msgSetVersionTx,
nodeUrl,
chainId,
}: {
msgSetVersionTx: MsgSetVersionTx;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const signerAddr = msgSetVersionTx.signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
const msgSetVersionObj = {
version: msgSetVersionTx.version,
signer: signerDecoded.data,
};
const versionMsg =
hermes.hermes.v1beta1.types.MsgSetVersion.fromObject(msgSetVersionObj);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(versionMsg)],
});
};
/**
* Structure a MsgSetNodeKeys
*
* @param {MsgSetPubkeysTx} msgSetPubkeysTx Msg of type `MsgSetPubkeysTx`.
* @param {string} nodeUrl Node url
* @param {chainId} ChainId Chain id of the network
*
* @returns {Tx} The transaction details of the given transaction id.
*
* @throws {"Invalid client url"} Thrown if the client url is an invalid one.
*/
export const buildSetPubkeysTx = async ({
msgSetNodePubkeysTx,
nodeUrl,
chainId,
}: {
msgSetNodePubkeysTx: MsgSetPubkeysTx;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const signerAddr = msgSetNodePubkeysTx.signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
const msgSetNodePubkeysObj = {
pubKeySetSet: {
secp256k1: msgSetNodePubkeysTx.secp256k1Pubkey,
ed25519: msgSetNodePubkeysTx.ed25519Pubkey,
},
validatorConsPubKey: msgSetNodePubkeysTx.validatorConsPubkey,
signer: signerDecoded.data,
};
const nodePubkeysMsg =
hermes.hermes.v1beta1.types.MsgSetNodeKeys.fromObject(msgSetNodePubkeysObj);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(nodePubkeysMsg)],
});
};
/**
* Structure a MsgSetIpAddress
*
* @param {MsgSetIpAddressTx} msgSetIpAddressTx Msg of type `MsgSetIpAddressTx`.
* @param {string} nodeUrl Node url
* @param {chainId} ChainId Chain id of the network
*
* @returns {Tx} The transaction details of the given transaction id.
*
* @throws {"Invalid client url"} Thrown if the client url is an invalid one.
*/
export const buildSetIpAddressTx = async ({
msgSetIpAddressTx,
nodeUrl,
chainId,
}: {
msgSetIpAddressTx: MsgSetIpAddressTx;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const signerAddr = msgSetIpAddressTx.signer.toString();
const signerDecoded = bech32Buffer.decode(signerAddr);
const msgSetIpAddressObj = {
ipAddress: msgSetIpAddressTx.ipAddress,
signer: signerDecoded.data,
};
const ipAddressMsg =
hermes.hermes.v1beta1.types.MsgSetIPAddress.fromObject(msgSetIpAddressObj);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(ipAddressMsg)],
});
};
/**
* Structure a MsgSend
*
* @param fromAddress - required, from address string
* @param toAddress - required, to address string
* @param assetAmount - required, asset amount string (e.g. "10000")
* @param assetDenom - required, asset denom string (e.g. "doj")
* @param memo - optional, memo string
*
* @returns
*/
export const buildTransferTx = async ({
fromAddress,
toAddress,
assetAmount,
assetDenom,
memo = "",
nodeUrl,
chainId,
}: {
fromAddress: Address;
toAddress: Address;
assetAmount: BaseAmount;
assetDenom: string;
memo?: string;
nodeUrl: string;
chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
const networkChainId = await getChainId(nodeUrl);
if (!networkChainId || chainId !== networkChainId) {
throw new Error(
`Invalid network (asked: ${chainId} / returned: ${networkChainId}`
);
}
const fromDecoded = bech32Buffer.decode(fromAddress);
const toDecoded = bech32Buffer.decode(toAddress);
const transferObj = {
fromAddress: fromDecoded.data,
toAddress: toDecoded.data,
amount: [
{
amount: assetAmount.amount().toString(),
denom: assetDenom,
},
],
};
const transferMsg =
hermes.hermes.v1beta1.types.MsgSend.fromObject(transferObj);
return new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.instanceToProtoAny(transferMsg)],
memo,
});
};
/**
* Get the balance of a given address.
*
* @param {Address} address By default, it will return the balance of the current wallet. (optional)
* @param {Asset} asset If not set, it will return all assets available. (optional)
* @param {cosmosClient} CosmosSDKClient
*
* @returns {Balance[]} The balance of the address.
*/
export const getBalance = async ({
address,
assets,
cosmosClient,
}: {
address: Address;
assets?: Asset[];
cosmosClient: CosmosSDKClient;
}): Promise<Balance[]> => {
const balances = await cosmosClient.getBalance(address);
if (balances.length === 0) {
const data = [
{
asset: AssetDOJNative,
amount: baseAmount(0, DOJ_DECIMAL),
},
];
return data;
} else {
return balances
.map((balance) => ({
asset:
(balance.denom && assetFromDenom(balance.denom)) || AssetDOJNative,
amount: baseAmount(balance.amount, DOJ_DECIMAL),
}))
.filter(
(balance) =>
!assets ||
assets.filter(
(asset) => assetToString(balance.asset) === assetToString(asset)
).length
);
}
};
/**
* Get the explorer url.
*
* @param {Network} network
* @param {ExplorerUrls} Explorer urls
* @returns {string} The explorer url for hermeschain based on the given network.
*/
export const getExplorerUrl = (
{ root }: ExplorerUrls,
network: Network
): string => root[network];
/**
* Get explorer address url.
*
* @param {ExplorerUrls} Explorer urls
* @param {Network} network
* @param {Address} address
* @returns {string} The explorer url for the given address.
*/
export const getExplorerAddressUrl = ({
urls,
network,
address,
}: {
urls: ExplorerUrls;
network: Network;
address: Address;
}): string => {
const url = `${urls.address[network]}/${address}`;
switch (network) {
case Network.Mainnet:
return url;
case Network.Stagenet:
return `${url}?network=stagenet`;
case Network.Testnet:
return `${url}?network=testnet`;
}
};
/**
* Get transaction url.
*
* @param {ExplorerUrls} Explorer urls
* @param {Network} network
* @param {TxHash} txID
* @returns {string} The explorer url for the given transaction id.
*/
export const getExplorerTxUrl = ({
urls,
network,
txID,
}: {
urls: ExplorerUrls;
network: Network;
txID: TxHash;
}): string => {
const url = `${urls.tx[network]}/${txID}`;
switch (network) {
case Network.Mainnet:
return url;
case Network.Stagenet:
return `${url}?network=stagenet`;
case Network.Testnet:
return `${url}?network=testnet`;
}
};
export type ComputeUnits = {
blockUnits: number;
txnUnits: number;
};