genlayer_deploy
Deploy a GenLayer Intelligent Contract with automatic private key handling. Accepts contract path and optional RPC, chain, and argument parameters.
Instructions
Deploy a GenLayer Intelligent Contract with genlayer-js. Accepts privateKey; generates one when missing or invalid.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| contractPath | Yes | Path to the Intelligent Contract source file. | |
| privateKey | No | GenLayer private key. If missing or invalid, a new private key is generated with genlayer-js. | |
| args | No | Constructor arguments passed to deployContract. | |
| kwargs | No | Keyword constructor arguments passed to deployContract. | |
| rpcUrl | No | Custom GenLayer RPC endpoint. | |
| chain | No | GenLayer chain used when creating the SDK client. | localnet |
| cwd | No | Base directory for resolving contractPath. Defaults to MCP server cwd. | |
| leaderOnly | No | ||
| consensusMaxRotations | No | ||
| initializeConsensus | No | ||
| autoFundLocalnet | No | Call localnet sim_fundAccount before deploy. Only works on localnet. | |
| fundAmount | No | ||
| waitForReceipt | No | ||
| receiptStatus | No | ACCEPTED | |
| receiptRetries | No | ||
| receiptIntervalMs | No | ||
| timeoutMs | No | ||
| exposePrivateKey | No | Return the generated/used private key in output. Defaults false to avoid leaking secrets. |
Implementation Reference
- src/index.ts:498-503 (registration)Registration of the 'genlayer_deploy' tool with the MCP server using server.tool(). Delegates to safeDeployWithSdk.
server.tool( "genlayer_deploy", "Deploy a GenLayer Intelligent Contract with genlayer-js. Accepts privateKey; generates one when missing or invalid.", deployInputShape, async (input) => textResponse(await safeDeployWithSdk(input)) ); - src/index.ts:265-362 (handler)Core handler function 'deployWithSdk' that performs the actual deployment logic: reads the contract file, normalizes/generates a private key, creates a genlayer-js client, optionally funds a localnet account, deploys the contract, optionally waits for a receipt, and returns the result.
async function deployWithSdk(input: z.infer<typeof deployInputSchema>) { const baseCwd = input.cwd ?? processCwd(); const contractPath = resolve(baseCwd, input.contractPath); if (!existsSync(contractPath)) { throw new Error(`Contract file not found: ${contractPath}`); } const configuredPrivateKey = getConfiguredPrivateKey(); const selectedPrivateKey = input.privateKey ?? configuredPrivateKey.value; const normalizedPrivateKey = normalizePrivateKey(selectedPrivateKey); const providedPrivateKeyValid = Boolean(normalizedPrivateKey); const generatedPrivateKey = !providedPrivateKeyValid; const privateKey = (normalizedPrivateKey ?? generatePrivateKey()) as `0x${string}`; const account = createAccount(privateKey); const contractCode = readFileSync(contractPath, "utf8"); if (!contractCode.trim()) { throw new Error(`Contract file is empty: ${contractPath}`); } const client = createClient({ chain: chains[input.chain], endpoint: input.rpcUrl, account }); const deployOperation = async () => { if (input.initializeConsensus) { await client.initializeConsensusSmartContract(); } let funded = false; let fundingError: string | undefined; if (input.autoFundLocalnet && input.chain === "localnet") { try { await (client as unknown as { fundAccount(args: { address: string; amount: number }): Promise<unknown> }) .fundAccount({ address: account.address, amount: input.fundAmount }); funded = true; } catch (error) { fundingError = error instanceof Error ? error.message : String(error); } } const transactionHash = await client.deployContract({ account, code: contractCode, args: input.args as Parameters<typeof client.deployContract>[0]["args"], kwargs: input.kwargs as Parameters<typeof client.deployContract>[0]["kwargs"], leaderOnly: input.leaderOnly, consensusMaxRotations: input.consensusMaxRotations }); const receipt = input.waitForReceipt ? await client.waitForTransactionReceipt({ hash: transactionHash as Parameters<typeof client.waitForTransactionReceipt>[0]["hash"], status: input.receiptStatus === "FINALIZED" ? TransactionStatus.FINALIZED : TransactionStatus.ACCEPTED, retries: input.receiptRetries, interval: input.receiptIntervalMs }) : undefined; const contractAddress = (receipt as { data?: { contract_address?: unknown }; txDataDecoded?: { contractAddress?: unknown } } | undefined) ?.data?.contract_address ?? (receipt as { txDataDecoded?: { contractAddress?: unknown } } | undefined)?.txDataDecoded ?.contractAddress; return { ok: true, contractPath, chain: input.chain, rpcUrl: input.rpcUrl, accountAddress: account.address, privateKeySource: providedPrivateKeyValid ? input.privateKey ? "input" : configuredPrivateKey.source : "generated", privateKey: generatedPrivateKey || input.exposePrivateKey ? privateKey : undefined, privateKeyExposed: generatedPrivateKey || input.exposePrivateKey, deployAttempted: true, funded, fundingError, transactionHash, contractAddress, receipt }; }; return await withTimeout(deployOperation(), input.timeoutMs); } - src/index.ts:428-437 (handler)Safe wrapper around deployWithSdk that catches errors and returns them as structured failure responses.
async function safeDeployWithSdk(input: z.infer<typeof deployInputSchema>) { try { return await deployWithSdk(input); } catch (error) { return { ok: false, error: error instanceof Error ? error.message : String(error) }; } } - src/index.ts:50-83 (schema)Zod schema definition for genlayer_deploy input parameters, defining all accepted fields with types, defaults, and descriptions.
const deployInputShape = { contractPath: z.string().describe("Path to the Intelligent Contract source file."), privateKey: z .string() .optional() .describe("GenLayer private key. If missing or invalid, a new private key is generated with genlayer-js."), args: z.array(z.unknown()).default([]).describe("Constructor arguments passed to deployContract."), kwargs: z.record(z.unknown()).optional().describe("Keyword constructor arguments passed to deployContract."), rpcUrl: z.string().url().optional().describe("Custom GenLayer RPC endpoint."), chain: z .enum(["localnet", "studionet", "testnetAsimov", "testnetBradbury"]) .default("localnet") .describe("GenLayer chain used when creating the SDK client."), cwd: z.string().optional().describe("Base directory for resolving contractPath. Defaults to MCP server cwd."), leaderOnly: z.boolean().default(false), consensusMaxRotations: z.number().int().positive().optional(), initializeConsensus: z.boolean().default(true), autoFundLocalnet: z .boolean() .default(true) .describe("Call localnet sim_fundAccount before deploy. Only works on localnet."), fundAmount: z.number().positive().default(10), waitForReceipt: z.boolean().default(true), receiptStatus: z.enum(["ACCEPTED", "FINALIZED"]).default("ACCEPTED"), receiptRetries: z.number().int().positive().default(50), receiptIntervalMs: z.number().int().positive().default(5000), timeoutMs: z.number().int().positive().max(30 * 60 * 1000).default(120_000), exposePrivateKey: z .boolean() .default(false) .describe("Return the generated/used private key in output. Defaults false to avoid leaking secrets.") }; const deployInputSchema = z.object(deployInputShape); - src/index.ts:379-394 (helper)Helper functions: normalizePrivateKey validates and normalizes private key format (0x-prefixed or raw hex); getConfiguredPrivateKey looks up private key from environment variables.
function normalizePrivateKey(privateKey: string | undefined): `0x${string}` | undefined { if (!privateKey) { return undefined; } const trimmed = privateKey.trim(); if (PRIVATE_KEY_RE.test(trimmed)) { return trimmed as `0x${string}`; } if (RAW_PRIVATE_KEY_RE.test(trimmed)) { return `0x${trimmed}`; } return undefined; }