UNPKG

@moonwall/util

Version:

Testing framework for the Moon family of projects

259 lines 11.7 kB
import { createWalletClient, hexToNumber, http, } from "viem"; import { setTimeout as timer } from "node:timers/promises"; import { privateKeyToAccount } from "viem/accounts"; import { ALITH_ADDRESS, ALITH_PRIVATE_KEY } from "../constants/accounts"; import { directRpcRequest } from "./common"; /** * @name getDevChain * @description This function returns a development chain object for Moonbeam. * @param url - The WebSocket URL of the development chain. * * @returns Returns an object that represents the Moonbeam development chain. * The object includes properties such as the chain's ID, name, network, native currency, and RPC URLs. * * @property id - The ID of the development chain. For Moonbeam Dev, this is 1281. * @property name - The name of the development chain. For this function, it's "Moonbeam Dev". * @property network - The network name of the development chain. For this function, it's "moonbeam". * @property nativeCurrency - An object containing the native currency's details: * - decimals: The number of decimal places the native currency supports. * - name: The name of the native currency. For Moonbeam Dev, it's "Glimmer". * - symbol: The symbol of the native currency. For Moonbeam Dev, it's "GLMR". * @property rpcUrls - An object that includes the RPC URLs for the chain: * - public: The public HTTP URL(s) for the chain. * - default: The default HTTP URL(s) for the chain. */ export async function getDevChain(url) { const httpUrl = url.replace("ws", "http"); const block = { http: [httpUrl] }; return { id: 1281, name: "Moonbeam Dev", nativeCurrency: { decimals: 18, name: "Glimmer", symbol: "GLMR", }, rpcUrls: { public: block, default: block, }, }; } /** * Derives a Viem chain object from a given HTTP endpoint. * * @export * @param endpoint The endpoint for the JSON RPC requests. * @returns A promise that resolves to an object satisfying the Chain interface, which includes * properties such as the chain id, chain name, network name, native currency information, * and RPC URLs. * @throws Will throw an error if the RPC request fails. * @example * const chain = await deriveViemChain('http://localhost:8545'); */ export async function deriveViemChain(endpoint) { const httpEndpoint = endpoint.replace("ws", "http"); const block = { http: [httpEndpoint] }; const id = hexToNumber(await directRpcRequest(httpEndpoint, "eth_chainId")); const name = await directRpcRequest(httpEndpoint, "system_chain"); const { tokenSymbol, tokenDecimals } = await directRpcRequest(httpEndpoint, "system_properties"); return { id, name, nativeCurrency: { decimals: tokenDecimals, name: tokenSymbol, symbol: tokenSymbol, }, rpcUrls: { public: block, default: block, }, }; } /** * @name deployViemContract * @description This function deploys a contract to the Moonbeam development chain. * @param context - The DevModeContext object. * @param abi - The Application Binary Interface (ABI) of the contract. * @param bytecode - The compiled bytecode of the contract. * @param privateKey - The private key used for the deployment transaction (defaults to ALITH_PRIVATE_KEY). * * @returns Returns an object containing the deployed contract's address, the transaction status, and any logs. * * @throws This function will throw an error if the contract deployment fails. * * @async This function returns a Promise that resolves when the contract has been successfully deployed. * * @property contractAddress - The address of the deployed contract. * @property status - The status of the contract deployment transaction. * @property logs - Any logs produced during the contract deployment transaction. */ export async function deployViemContract( // TODO: Make this generic context, abi, bytecode, options) { // Enable when Viem allows it // const isLegacy = options?.txnType === "legacy" || options?.txnType === undefined; // const isEIP1559 = options?.txnType === "eip1559"; // const isEIP2930 = options?.txnType === "eip2930"; // const url = context.viem().transport.url; const url = context.viem().transport.url; const { privateKey = ALITH_PRIVATE_KEY, ...rest } = options || {}; const blob = { ...rest, abi, bytecode, account: privateKeyToAccount(privateKey), }; const account = privateKeyToAccount(ALITH_PRIVATE_KEY); const client = createWalletClient({ transport: http(url), account, chain: await deriveViemChain(url), }); // Enable when Viem allows it // switch (true) { // case isLegacy: // blob["gasPrice"] = options?.gasPrice || 10_000_000_000n; // blob["gas"] = options?.gasLimit || 22318; // break; // case isEIP1559: // blob["accessList"] = options?.accessList || []; // blob["maxFeePerGas"] = options?.maxFeePerGas || 10_000_000_000n; // blob["maxPriorityFeePerGas"] = options?.maxPriorityFeePerGas || 0n; // blob["gasLimit"] = options?.gasLimit || 22318; // break; // case isEIP2930: // blob["gasPrice"] = options?.gasPrice || 10_000_000_000n; // blob["gasLimit"] = options?.gasLimit || 22318n; // blob["accessList"] = options?.accessList || []; // break; // default: // throw new Error("Invalid transaction type, undpate deployViemContract function"); // } const hash = await client.deployContract(blob); await context.createBlock(); for (let i = 0; i < 5; i++) { try { const { contractAddress, status, logs } = await context .viem() .getTransactionReceipt({ hash }); return { contractAddress, status, logs, hash }; } catch (e) { console.log(e.message); console.log("Contract deployment query, retrying..."); await timer(100); } } throw new Error("Contract deployment query failed after 5 retries"); } /** * createRawTransfer function creates and signs a transfer, as a hex string, that can be submitted to the network via public client." * * @export * @template TOptions - Optional parameters of Viem's TransferOptions * @param {DevModeContext} context - the DevModeContext instance * @param {`0x${string}`} to - the destination address of the transfer * @param {InputAmountFormats} value - the amount to transfer. It accepts different formats including number, bigint, string or hexadecimal strings * @param {TOptions} [options] - (optional) additional transaction options * @returns {Promise<`0x${string}`>} - the signed raw transaction in hexadecimal string format */ export async function createRawTransfer(context, to, value, options) { const transferAmount = typeof value === "bigint" ? value : BigInt(value); return await createViemTransaction(context, { ...options, to: to, value: transferAmount, }); } /** * createViemTransaction function creates and signs a raw transaction, as a hex string, that can be submitted to the network via public client." * * @export * @template TOptions - Optional parameters of Viem's TransactionOptions * @param {GenericContext} context - the GenericContext instance * @param {TOptions} options - transaction options including type, privateKey, value, to, chainId, gasPrice, estimatedGas, accessList, data * @returns {Promise<string>} - the signed raw transaction in hexadecimal string format */ export async function createViemTransaction(context, options) { const type = !!options && !!options.txnType ? options.txnType : "eip1559"; const privateKey = !!options && !!options.privateKey ? options.privateKey : ALITH_PRIVATE_KEY; const account = privateKeyToAccount(privateKey); const value = options?.value ? options.value : 0n; const to = options?.to ? options.to : "0x0000000000000000000000000000000000000000"; const chainId = await context.viem().getChainId(); const txnCount = await context.viem().getTransactionCount({ address: account.address }); const gasPrice = await context.viem().getGasPrice(); const data = options?.data ? options.data : "0x"; const estimatedGas = options.skipEstimation || options.gas !== undefined ? 1500000n : await context.viem().estimateGas({ account: account.address, to, value, data }); const accessList = options?.accessList ? options.accessList : []; const txnBlob = type === "eip1559" ? { to, value, maxFeePerGas: options.maxFeePerGas !== undefined ? options.maxFeePerGas : gasPrice, maxPriorityFeePerGas: options.maxPriorityFeePerGas !== undefined ? options.maxPriorityFeePerGas : gasPrice, gas: options.gas !== undefined ? options.gas : estimatedGas, nonce: options.nonce !== undefined ? options.nonce : txnCount, data, chainId, type, } : type === "legacy" ? { to, value, gasPrice: options.gasPrice !== undefined ? options.gasPrice : gasPrice, gas: options.gas !== undefined ? options.gas : estimatedGas, nonce: options.nonce !== undefined ? options.nonce : txnCount, data, } : type === "eip2930" ? { to, value, gasPrice: options.gasPrice !== undefined ? options.gasPrice : gasPrice, gas: options.gas !== undefined ? options.gas : estimatedGas, nonce: options.nonce !== undefined ? options.nonce : txnCount, data, chainId, type, } : {}; if ((type === "eip1559" && accessList.length > 0) || (type === "eip2930" && accessList.length > 0)) { txnBlob.accessList = accessList; } return await account.signTransaction(txnBlob); } /** * checkBalance function checks the balance of a given account. * * @export * @param {DevModeContext} context - the DevModeContext instance * @param {`0x${string}`} [account=ALITH_ADDRESS] - the account address whose balance is to be checked. If no account is provided, it defaults to ALITH_ADDRESS * @returns {Promise<bigint>} - returns a Promise that resolves to the account's balance as a BigInt */ export async function checkBalance(context, account = ALITH_ADDRESS, block = "latest") { return typeof block === "string" ? await context.viem().getBalance({ address: account, blockTag: block }) : typeof block === "bigint" ? await context.viem().getBalance({ address: account, blockNumber: block }) : await context.viem().getBalance({ address: account }); } /** * Sends a raw signed transaction on to RPC node for execution. * * @async * @function * @param {GenericContext} context - The DevModeContext for the Ethereum client interaction. * @param {`0x${string}`} rawTx - The signed and serialized hexadecimal transaction string. * @returns {Promise<any>} A Promise resolving when the transaction is sent or rejecting with an error. */ export async function sendRawTransaction(context, rawTx) { return await context.viem().request({ method: "eth_sendRawTransaction", params: [rawTx] }); } //# sourceMappingURL=viem.js.map