diff --git a/.changeset/unlucky-bulldogs-obey.md b/.changeset/unlucky-bulldogs-obey.md new file mode 100644 index 00000000..2c6a85b5 --- /dev/null +++ b/.changeset/unlucky-bulldogs-obey.md @@ -0,0 +1,8 @@ +--- +"@moonwall/types": patch +"@moonwall/util": patch +"@moonwall/cli": patch +--- + +Added log saving +- [#175](https://github.com/Moonsong-Labs/moonwall/issues/175) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 680b0380..48a46a01 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ dist/ html/* *.sqlite +**/*.log downloads/* *.wasm moonbeam diff --git a/moonwall.config.json b/moonwall.config.json index 922c22f7..6d3ba7ed 100644 --- a/moonwall.config.json +++ b/moonwall.config.json @@ -390,6 +390,7 @@ "name": "moonbeam", "running": false, "binPath": "tmp/moonbeam", + "retainAllLogs": true, "ports": { "p2pPort": 30333, "wsPort": 9944, "rpcPort": 9933 }, "options": [ "--dev", diff --git a/package.json b/package.json index f2cb1a7a..99bf7ddf 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "changeset": "changeset", "changeset:release": "changeset publish", "changeset:version": "changeset version", + "clean-all":"rimraf node_modules && pnpm -r --filter='./packages/**' run clean", "start": "pnpm exec moonwall", "display-reports": "pnpm exec vite preview --base __vitest__ --outDir html", "test": "pnpm exec moonwall test 'basic chopsticks dev_seq chop_state_test'", diff --git a/packages/cli/package.json b/packages/cli/package.json index bb29a1a7..a8f3dcde 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -48,6 +48,7 @@ "*.cjs" ], "scripts": { + "clean":"pnpm rimraf dist && pnpm rimraf node_modules", "build": "pnpm exec rimraf dist && tsup src --format cjs,esm --config tsup.config.ts && pnpm generate-types", "generate-types": "tsup src --format cjs,esm --dts --config tsup.config.ts", "watch": "tsup src --format cjs,esm --dts --watch", diff --git a/packages/cli/src/cmds/runNetwork.ts b/packages/cli/src/cmds/runNetwork.ts index cf45dc5c..3eb7e6f3 100644 --- a/packages/cli/src/cmds/runNetwork.ts +++ b/packages/cli/src/cmds/runNetwork.ts @@ -12,6 +12,7 @@ import { parse } from "yaml"; import { importJsonConfig, loadEnvVars } from "../lib/configReader.js"; import { MoonwallContext, runNetworkOnly } from "../lib/globalContext.js"; import { executeTests } from "./runTests.js"; +import { clearNodeLogs, reportLogLocation } from "src/internal/cmdFunctions/tempLogs.js"; inquirer.registerPrompt("press-to-continue", PressToContinuePrompt); @@ -30,11 +31,8 @@ export async function runNetwork(args) { } await loadEnvVars(); - const testFileDirs = globalConfig.environments.find( - ({ name }) => name == args.envName - )!.testFileDir; - const foundation = globalConfig.environments.find(({ name }) => name == args.envName)!.foundation - .type; + const testFileDirs = env.testFileDir; + const foundation = env.foundation.type; const questions = [ { @@ -121,10 +119,14 @@ export async function runNetwork(args) { }, ]; + if (env.foundation.type == "dev" && !env.foundation.launchSpec[0].retainAllLogs) { + clearNodeLogs(); + } + await runNetworkOnly(globalConfig); clear(); const portsList = await reportServicePorts(); - + reportLogLocation(); portsList.forEach(({ port }) => console.log(` 🖥️ https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A${port}`) ); @@ -308,6 +310,7 @@ const resolveInfoChoice = async (env: Environment) => { console.log(chalk.bgWhite.blackBright("Launch Spec in Config File:")); console.dir(env, { depth: null }); const portsList = await reportServicePorts(); + reportLogLocation(); portsList.forEach(({ port }) => console.log(` 🖥️ https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A${port}`) ); diff --git a/packages/cli/src/cmds/runTests.ts b/packages/cli/src/cmds/runTests.ts index f2bcf094..99f4a5ac 100644 --- a/packages/cli/src/cmds/runTests.ts +++ b/packages/cli/src/cmds/runTests.ts @@ -7,13 +7,13 @@ import fs from "node:fs"; import path from "path"; import chalk from "chalk"; import { execSync } from "node:child_process"; -import { filterCallsSome } from "@polkadot/types/metadata/decorate/index.js"; +import { clearNodeLogs } from "src/internal/cmdFunctions/tempLogs.js"; export async function testCmd(envName: string, additionalArgs?: {}) { const globalConfig = await importJsonConfig(); const env = globalConfig.environments.find(({ name }) => name === envName)!; process.env.MOON_TEST_ENV = envName; - // process.env.MOON_RUN_SCRIPTS = "true"; + if (!!!env) { const envList = globalConfig.environments.map((env) => env.name); throw new Error( @@ -63,7 +63,9 @@ export async function testCmd(envName: string, additionalArgs?: {}) { } } } - + if (env.foundation.type == "dev" && !env.foundation.launchSpec[0].retainAllLogs) { + clearNodeLogs(); + } const vitest = await executeTests(env, additionalArgs); const failed = vitest!.state.getFiles().filter((file) => file.result!.state === "fail"); diff --git a/packages/cli/src/internal/cmdFunctions/tempLogs.ts b/packages/cli/src/internal/cmdFunctions/tempLogs.ts new file mode 100644 index 00000000..9b563ee4 --- /dev/null +++ b/packages/cli/src/internal/cmdFunctions/tempLogs.ts @@ -0,0 +1,34 @@ +import path from "path"; +import fs from "fs"; + +export function clearNodeLogs() { + const dirPath = path.join(process.cwd(), "tmp", "node_logs"); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + + // Check for existing log files and delete + const files = fs.readdirSync(dirPath); + for (const file of files) { + if (file.endsWith(".log")) { + fs.unlinkSync(path.join(dirPath, file)); + } + } +} + +export function reportLogLocation() { + const dirPath = path.join(process.cwd(), "tmp", "node_logs"); + const result = fs.readdirSync(dirPath); + let filePath = ""; + try { + filePath = ` 🪵 Log location: ${path.join( + dirPath, + result.find((file) => path.extname(file) == ".log")! + )}`; + } catch (e) { + console.error(e); + } + + console.log(filePath); + return filePath; +} diff --git a/packages/cli/src/internal/localNode.ts b/packages/cli/src/internal/localNode.ts index 8f16170d..2e8e8a9d 100644 --- a/packages/cli/src/internal/localNode.ts +++ b/packages/cli/src/internal/localNode.ts @@ -2,6 +2,8 @@ import { ChildProcess, spawn } from "child_process"; import chalk from "chalk"; import Debug from "debug"; import { checkAccess, checkExists } from "./fileCheckers.js"; +import fs from "fs"; +import path from "path"; const debugNode = Debug("global:node"); export async function launchNode(cmd: string, args: string[], name: string): Promise { @@ -12,6 +14,8 @@ export async function launchNode(cmd: string, args: string[], name: string): Pro let runningNode: ChildProcess; + const dirPath = path.join(process.cwd(), "tmp", "node_logs"); + const onProcessExit = () => { runningNode && runningNode.kill(); }; @@ -21,11 +25,22 @@ export async function launchNode(cmd: string, args: string[], name: string): Pro process.once("exit", onProcessExit); process.once("SIGINT", onProcessInterrupt); + runningNode = spawn(cmd, args); + const fsStream = fs.createWriteStream( + path.join( + dirPath, + `${path.basename(cmd)}_node_${args.find((a) => a.includes("port"))?.split("=")[1]}_${ + runningNode.pid + }.log` + ) + ); + runningNode.once("exit", () => { process.removeListener("exit", onProcessExit); process.removeListener("SIGINT", onProcessInterrupt); + fsStream.end(); // This line ensures that the writable stream is properly closed debugNode(`Exiting dev node: ${name}`); }); @@ -40,6 +55,15 @@ export async function launchNode(cmd: string, args: string[], name: string): Pro process.exit(1); }); + const writeLogToFile = (chunk: any) => { + fsStream.write(chunk, (err) => { + if (err) console.error(err); + else fsStream.emit("drain"); + }); + }; + runningNode.stderr?.on("data", writeLogToFile); + runningNode.stdout?.on("data", writeLogToFile); + const binaryLogs: any[] = []; await new Promise((resolve, reject) => { const timer = setTimeout(() => { diff --git a/packages/types/config_schema.json b/packages/types/config_schema.json index 2aa972bd..56acd042 100644 --- a/packages/types/config_schema.json +++ b/packages/types/config_schema.json @@ -5,6 +5,7 @@ "description": "A launch specification object for the \"chopsticks\" foundation type.", "properties": { "buildBlockMode": { + "description": "An optional block building mode, can be \"batch\", \"manual\" or \"instant\".\nThis is only supported for single mode chopsticks.", "enum": [ "batch", "instant", @@ -13,21 +14,26 @@ "type": "string" }, "configPath": { + "description": "The path to the config file.", "type": "string" }, "name": { + "description": "The name of the launch spec.", "type": "string" }, "options": { + "description": "An optional array of options for the launch spec.", "items": { "type": "string" }, "type": "array" }, "running": { + "description": "UNUSED", "type": "boolean" }, "type": { + "description": "An optional type of either \"relaychain\" or \"parachain\".", "enum": [ "parachain", "relaychain" @@ -35,9 +41,11 @@ "type": "string" }, "wasmOverride": { + "description": "An optional WebAssembly override.", "type": "string" }, "wsPort": { + "description": "An optional WebSocket port.\nQuirk of Chopsticks is that port option is only for single mode not xcm.", "type": "number" } }, @@ -47,38 +55,52 @@ "description": "A launch specification object for the \"dev\" foundation type.", "properties": { "binPath": { + "description": "The path to the binary file.", "type": "string" }, "disableDefaultEthProviders": { + "description": "Switch to not connect to Ethereum providers by default.", "type": "boolean" }, "name": { + "description": "The name of the launch spec.", "type": "string" }, "newRpcBehaviour": { + "description": "Launch node using rpc-port parameter instead of ws-port.", "type": "boolean" }, "options": { + "description": "An optional array of options for the launch spec.", "items": { "type": "string" }, "type": "array" }, "ports": { + "description": "An optional object with p2pPort, wsPort, and rpcPort.", "properties": { "p2pPort": { + "description": "The port for peer-to-peer (P2P) communication.", "type": "number" }, "rpcPort": { + "description": "The port for remote procedure call (RPC).", "type": "number" }, "wsPort": { + "description": "The port for WebSocket communication (soon deprecated)", "type": "number" } }, "type": "object" }, + "retainAllLogs": { + "description": "An optional flag to retain node logs from previous runs.", + "type": "boolean" + }, "running": { + "description": "UNUSED", "type": "boolean" } }, @@ -212,23 +234,27 @@ }, "type": "object" }, - "description": "Represents a collection of GenericData.\nIt's an object where each key is a string and the corresponding value is a GenericData object.", + "description": "An optional collection of additional types.", "type": "object" }, "endpoints": { + "description": "An array of endpoint URLs.", "items": { "type": "string" }, "type": "array" }, "name": { + "description": "The name of the provider.", "type": "string" }, "rpc": { - "$ref": "#/definitions/IRpcBundle" + "$ref": "#/definitions/IRpcBundle", + "description": "An optional RPC bundle." }, "type": { - "$ref": "#/definitions/ProviderType" + "$ref": "#/definitions/ProviderType", + "description": "The type of the provider." } }, "type": "object" @@ -249,24 +275,30 @@ "description": "A launch specification object for the \"zombie\" foundation type.", "properties": { "configPath": { + "description": "The path to the config file.", "type": "string" }, "monitoredNode": { + "description": "An optional monitored node.", "type": "string" }, "name": { + "description": "The name of the launch spec.", "type": "string" }, "options": { + "description": "An optional array of options for the launch spec.", "items": { "type": "string" }, "type": "array" }, "running": { + "description": "UNUSED", "type": "boolean" }, "skipBlockCheck": { + "description": "An optional array of blocks to skip checking.", "items": { "type": "string" }, @@ -279,36 +311,46 @@ "description": "The main configuration object for Moonwall.", "properties": { "$schema": { + "description": "The JSON schema for the config.", "type": "string" }, "defaultTestTimeout": { + "description": "The default timeout for tests.", "type": "number" }, "environments": { + "description": "An array of Environment objects for testing.", "items": { "description": "The environment configuration for testing.", "properties": { "connections": { + "description": "An optional array of ProviderConfig objects.", "items": { "$ref": "#/definitions/ProviderConfig" }, "type": "array" }, "contracts": { + "description": "Path to directory containing smart contracts for testing against.", "type": "string" }, "defaultAllowFailures": { + "description": "Toggle whether createBlock() will throw when extrinsic errors inside.", "type": "boolean" }, "defaultFinalization": { + "description": "Toggle whether createBlock() will finalize blocks by default or not.", "type": "boolean" }, "defaultSigner": { + "description": "The privateKey with which to sign and send transactions in createBlock() function.", "properties": { "privateKey": { + "description": "Hex encoded private key to generate KeyringPair (\"0x..\")", "type": "string" }, "type": { + "description": "Substrate Keyring type", "enum": [ "ed25519", "ethereum", @@ -320,42 +362,50 @@ "type": "object" }, "envVars": { + "description": "An optional array of environment variable names.", "items": { "type": "string" }, "type": "array" }, "foundation": { - "$ref": "#/definitions/IFoundation" + "$ref": "#/definitions/IFoundation", + "description": "The foundation configuration for the environment." }, "include": { + "description": "An optional array of included files or directories.", "items": { "type": "string" }, "type": "array" }, "multiThreads": { + "description": "An optional boolean to indicate if multi-threading is enabled.", "type": [ "number", "boolean" ] }, "name": { + "description": "The name of the environment.", "type": "string" }, "reporters": { + "description": "An optional array of reporter names.", "items": { "type": "string" }, "type": "array" }, "runScripts": { + "description": "An optional array of scripts to run before testing.", "items": { "type": "string" }, "type": "array" }, "testFileDir": { + "description": "An array of directories with test files.", "items": { "type": "string" }, @@ -367,9 +417,11 @@ "type": "array" }, "label": { + "description": "A label for the config.", "type": "string" }, "scriptsDir": { + "description": "Optional path to a directory containing scripts.", "type": "string" } }, diff --git a/packages/types/package.json b/packages/types/package.json index 5f2f4233..3e320862 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -45,12 +45,13 @@ "*.cjs" ], "scripts": { + "clean":"pnpm rimraf dist && pnpm rimraf node_modules", "build": "pnpm exec rimraf dist && tsup src --format cjs,esm --config tsup.config.ts && pnpm generate-types", "generate-types": "tsup src --format cjs,esm --dts --config tsup.config.ts && pnpm schema && pnpm typecheck", "watch": "tsup src --format cjs,esm --dts --watch", "typecheck": "pnpm exec tsc --noEmit", "prepublish": "pnpm run build && pnpm run generate-types && pnpm typecheck", - "schema": "typescript-json-schema ./src/config.ts MoonwallConfig > config_schema.json", + "schema": "typescript-json-schema --tsNodeRegister --esModuleInterop -o config_schema.json ./src/config.ts MoonwallConfig", "prepare": "pnpm build", "compile": "pnpm build ", "lint": "tsc" diff --git a/packages/types/src/config.ts b/packages/types/src/config.ts index 390dd073..d4f8cf8a 100644 --- a/packages/types/src/config.ts +++ b/packages/types/src/config.ts @@ -1,50 +1,108 @@ /** - * @name MoonwallConfig - * @description The main configuration object for Moonwall. - * @property $schema - The JSON schema for the config. - * @property label - A label for the config. - * @property defaultTestTimeout - The default timeout for tests. - * @property scriptsDir - Optional path to a directory containing scripts. - * @property environments - An array of Environment objects for testing. + * The main configuration object for Moonwall. */ export type MoonwallConfig = { + /** + * The JSON schema for the config. + */ $schema: string; + + /** + * A label for the config. + */ label: string; + + /** + * The default timeout for tests. + */ defaultTestTimeout: number; + + /** + * Optional path to a directory containing scripts. + */ scriptsDir?: string; + + /** + * An array of Environment objects for testing. + */ environments: Environment[]; }; /** - * @name Environment - * @description The environment configuration for testing. - * @property reporters - An optional array of reporter names. - * @property name - The name of the environment. - * @property testFileDir - An array of directories with test files. - * @property envVars - An optional array of environment variable names. - * @property foundation - The foundation configuration for the environment. - * @property include - An optional array of included files or directories. - * @property connections - An optional array of ProviderConfig objects. - * @property multiThreads - An optional boolean to indicate if multi-threading is enabled. - * @property contracts - Path to foundry directory containing smart contracts for testing. - * @property runScripts - An optional array of scripts to run before testing. - * @property defaultSigner - The privateKey with which to sign and send transactions in createBlock() function. - * @property defaultAllowFailures - Toggle whether createBlock() will throw when extrinsic errors inside. - * @property defaultFinalization - Toggle whether createBlock() will finalize blocks by default or not. + * The environment configuration for testing. */ export type Environment = { + /** + * An optional array of reporter names. + */ reporters?: string[]; + + /** + * The name of the environment. + */ name: string; + + /** + * An array of directories with test files. + */ testFileDir: string[]; + + /** + * An optional array of environment variable names. + */ envVars?: string[]; + + /** + * The foundation configuration for the environment. + */ foundation: IFoundation; + + /** + * An optional array of included files or directories. + */ include?: string[]; + + /** + * An optional array of ProviderConfig objects. + */ connections?: ProviderConfig[]; + + /** + * An optional boolean to indicate if multi-threading is enabled. + */ multiThreads?: boolean | number; + + /** + * Path to directory containing smart contracts for testing against. + */ contracts?: string; + + /** + * An optional array of scripts to run before testing. + */ runScripts?: string[]; - defaultSigner?: { type: "ethereum" | "sr25519" | "ed25519"; privateKey: string }; + + /** + * The privateKey with which to sign and send transactions in createBlock() function. + */ + defaultSigner?: { + /** + * Substrate Keyring type + */ + type: "ethereum" | "sr25519" | "ed25519"; + /** + * Hex encoded private key to generate KeyringPair ("0x..") + */ + privateKey: string }; + + /** + * Toggle whether createBlock() will throw when extrinsic errors inside. + */ defaultAllowFailures?: boolean; + + /** + * Toggle whether createBlock() will finalize blocks by default or not. + */ defaultFinalization?: boolean; }; @@ -86,84 +144,153 @@ export const EthTransactionTypes = ["eip1559", "eip2930", "legacy"] as const; export type FoundationType = IFoundation["type"]; /** - * @name GenericLaunchSpec - * @description A generic launch specification object. - * @property name - The name of the launch spec. - * @property running - An optional flag indicating if the spec is currently running. - * @property options - An optional array of options for the launch spec. + * A generic launch specification object. */ export interface GenericLaunchSpec { + /** + * The name of the launch spec. + */ name: string; + + /** + * UNUSED + */ running?: boolean; + + /** + * An optional array of options for the launch spec. + */ options?: string[]; } /** - * @name ZombieLaunchSpec - * @description A launch specification object for the "zombie" foundation type. + * A launch specification object for the "zombie" foundation type. * @extends GenericLaunchSpec - * @property configPath - The path to the config file. - * @property monitoredNode - An optional monitored node. - * @property skipBlockCheck - An optional array of blocks to skip checking. */ export interface ZombieLaunchSpec extends GenericLaunchSpec { + /** + * The path to the config file. + */ configPath: string; + + /** + * An optional monitored node. + */ monitoredNode?: string; + + /** + * An optional array of blocks to skip checking. + */ skipBlockCheck?: string[]; } // TODO: Separate single chopsticks network and multi chopsticks into separate interfaces /** - * @name ChopsticksLaunchSpec - * @description A launch specification object for the "chopsticks" foundation type. + * A launch specification object for the "chopsticks" foundation type. * @extends GenericLaunchSpec - * @property configPath - The path to the config file. - * @property wsPort - An optional WebSocket port. - * @property type - An optional type of either "relaychain" or "parachain". - * @property wasmOverride - An optional WebAssembly override. - * @property buildBlockMode - An optional block building mode, can be "batch", "manual" or "instant". */ export interface ChopsticksLaunchSpec extends GenericLaunchSpec { + /** + * The path to the config file. + */ configPath: string; - wsPort?: number; // Quirk of Chopsticks is that port option only for single mode not xcm + + /** + * An optional WebSocket port. + * Quirk of Chopsticks is that port option is only for single mode not xcm. + */ + wsPort?: number; + + /** + * An optional type of either "relaychain" or "parachain". + */ type?: "relaychain" | "parachain"; + + /** + * An optional WebAssembly override. + */ wasmOverride?: string; - // buildBlockMode only supported for single mode chopsticks + + /** + * An optional block building mode, can be "batch", "manual" or "instant". + * This is only supported for single mode chopsticks. + */ buildBlockMode?: "batch" | "manual" | "instant"; } /** - * @name DevLaunchSpec - * @description A launch specification object for the "dev" foundation type. + * A launch specification object for the "dev" foundation type. * @extends GenericLaunchSpec - * @property binPath - The path to the binary file. - * @property disableDefaultEthProviders - An optional flag to disable default Ethereum providers. - * @property ports - An optional object with p2pPort, wsPort, and rpcPort. */ export interface DevLaunchSpec extends GenericLaunchSpec { + /** + * The path to the binary file. + */ binPath: string; + + /** + * Switch to not connect to Ethereum providers by default. + */ disableDefaultEthProviders?: boolean; + + /** + * Launch node using rpc-port parameter instead of ws-port. + */ newRpcBehaviour?: boolean; + + /** + * An optional flag to retain node logs from previous runs. + */ + retainAllLogs?: boolean; + + /** + * An optional object with p2pPort, wsPort, and rpcPort. + */ ports?: { + /** + * The port for peer-to-peer (P2P) communication. + */ p2pPort: number; + + /** + * The port for remote procedure call (RPC). + */ rpcPort: number; + + /** + * The port for WebSocket communication (soon deprecated) + */ wsPort: number; }; } /** - * @name ProviderConfig - * @description The configuration object for a provider. - * @property name - The name of the provider. - * @property type - The type of the provider. - * @property endpoints - An array of endpoint URLs. - * @property rpc - An optional RPC bundle. + * The configuration object for a provider. */ export interface ProviderConfig { + /** + * The name of the provider. + */ name: string; + + /** + * The type of the provider. + */ type: ProviderType; + + /** + * An array of endpoint URLs. + */ endpoints: string[]; + + /** + * An optional RPC bundle. + */ rpc?: IRpcBundle; + + /** + * An optional collection of additional types. + */ additionalTypes?: TypesBundle; } diff --git a/packages/util/package.json b/packages/util/package.json index fdd0c882..db3fac7b 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -45,6 +45,7 @@ "*.cjs" ], "scripts": { + "clean":"pnpm rimraf dist && pnpm rimraf node_modules", "build": "pnpm exec rimraf dist && tsup src --format cjs,esm --config tsup.config.ts && pnpm generate-types", "generate-types": "tsup src --format cjs,esm --dts --config tsup.config.ts", "watch": "tsup src --format cjs,esm --dts --watch", diff --git a/packages/util/src/functions/ethers.ts b/packages/util/src/functions/ethers.ts index 71d6191e..8a64ef82 100644 --- a/packages/util/src/functions/ethers.ts +++ b/packages/util/src/functions/ethers.ts @@ -1,5 +1,5 @@ import { DevModeContext } from "@moonwall/types"; -import { TransactionRequest } from "ethers"; +import { TransactionRequest, Wallet } from "ethers"; import { TransactionType } from "@moonwall/types"; import { ALITH_ADDRESS } from "../constants/accounts.js"; @@ -27,7 +27,7 @@ const transactionHandlers: Record = { }; export async function createEthersTxn< - TOptions extends TransactionRequest & { txnType?: TransactionType } + TOptions extends TransactionRequest & { txnType?: TransactionType; privateKey?: `0x${string}` } >(context: DevModeContext, params: TOptions) { const nonce = await context.viem("public").getTransactionCount({ address: ALITH_ADDRESS }); const blob: {} = { nonce, ...params }; @@ -38,7 +38,11 @@ export async function createEthersTxn< } handler(blob, params); - const txn = await context.ethers().populateTransaction(blob); - const raw = await context.ethers().signTransaction(txn); + const signer = params.privateKey + ? new Wallet(params.privateKey, context.ethers().provider) + : context.ethers(); + + const txn = await signer.populateTransaction(blob); + const raw = await signer.signTransaction(txn); return { rawSigned: raw as `0x${string}`, request: txn }; } diff --git a/packages/util/src/functions/viem.ts b/packages/util/src/functions/viem.ts index 74443151..ab4dea4a 100644 --- a/packages/util/src/functions/viem.ts +++ b/packages/util/src/functions/viem.ts @@ -172,6 +172,7 @@ export type TransferOptions = export type ViemTransactionOptions = | TransactionSerializable & { privateKey?: `0x${string}`; + skipEstimation?: boolean; }; /** @@ -218,9 +219,9 @@ export async function createRawTransaction