From 04f0e5796dc592ce70ac82a69baa706d6bd19db4 Mon Sep 17 00:00:00 2001 From: "italiano@oplabs.co" Date: Fri, 5 Apr 2024 10:41:37 -0400 Subject: [PATCH] Adds deployment endpoint --- apps/dapp-console-api/src/Service.ts | 5 +- apps/dapp-console-api/src/api/ApiV0.ts | 3 + .../src/routes/Deployments.ts | 153 ++++++++++++++++++ 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 apps/dapp-console-api/src/routes/Deployments.ts diff --git a/apps/dapp-console-api/src/Service.ts b/apps/dapp-console-api/src/Service.ts index 92eee53b..970924ed 100644 --- a/apps/dapp-console-api/src/Service.ts +++ b/apps/dapp-console-api/src/Service.ts @@ -25,6 +25,7 @@ import { connectToDatabase, runMigrations } from './db' import { metrics } from './monitoring/metrics' import { AuthRoute } from './routes/auth' import { WalletsRoute } from './routes/wallets' +import { DeploymentRoute } from './routes/Deployments' import { Trpc } from './Trpc' import { retryWithBackoff } from './utils' @@ -125,11 +126,12 @@ export class Service { const authRoute = new AuthRoute(trpc) const walletsRoute = new WalletsRoute(trpc) + const deploymentRoute = new DeploymentRoute(trpc) /** * The apiServer simply assmbles the routes into a TRPC Server */ - const apiServer = new ApiV0(trpc, { authRoute, walletsRoute }) + const apiServer = new ApiV0(trpc, { authRoute, deploymentRoute, walletsRoute }) apiServer.setLoggingServer(logger) const adminServer = new AdminApi(trpc, {}) @@ -302,7 +304,6 @@ export class Service { } private readonly routes = async (router: Router) => { - router.use(this.middleware.cors) // These handlers do nothing. they pass on the request to the next handler. // They are a hack I added because our trpc middleware does not expose the supported routes // in the canonical way and we need a way to filter invalid request paths from being logged to diff --git a/apps/dapp-console-api/src/api/ApiV0.ts b/apps/dapp-console-api/src/api/ApiV0.ts index 9d9e0737..abb9ccc0 100644 --- a/apps/dapp-console-api/src/api/ApiV0.ts +++ b/apps/dapp-console-api/src/api/ApiV0.ts @@ -1,5 +1,6 @@ import type { AuthRoute } from '@/routes/auth' import type { WalletsRoute } from '@/routes/wallets' +import type { DeploymentRoute } from '@/routes/Deployments' import { MajorApiVersion } from '../constants' import type { Trpc } from '../Trpc' @@ -32,6 +33,7 @@ export class ApiV0 extends Api { ...this.commonRoutes, [this.routes.authRoute.name]: this.routes.authRoute.handler, [this.routes.walletsRoute.name]: this.routes.walletsRoute.handler, + [this.routes.deploymentRoute.name]: this.routes.deploymentRoute.handler, }) /** @@ -42,6 +44,7 @@ export class ApiV0 extends Api { protected readonly routes: { authRoute: AuthRoute walletsRoute: WalletsRoute + deploymentRoute: DeploymentRoute }, ) { super(trpc) diff --git a/apps/dapp-console-api/src/routes/Deployments.ts b/apps/dapp-console-api/src/routes/Deployments.ts new file mode 100644 index 00000000..484a6175 --- /dev/null +++ b/apps/dapp-console-api/src/routes/Deployments.ts @@ -0,0 +1,153 @@ +import type { Abi, Address } from 'viem' +import { + createPublicClient, + createWalletClient, + encodeDeployData, + http, + isHex, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { anvil } from 'viem/chains' +import { z } from 'zod' + +import { Trpc } from '..' +import { Route } from './Route' + +const DEPLOY_SALT = + '0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC' + +const create2FactoryContractAddress = + '0x5FbDB2315678afecb367f032d93F642f64180aa3' + +const create2FactoryAbi = [ + { + type: 'function', + name: 'computeAddress', + inputs: [ + { + name: 'bytecode', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'salt', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'deploy', + inputs: [ + { + name: 'bytecode', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'salt', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'DeployedContract', + inputs: [ + { + name: 'addr', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, +] as Abi + +const deployer = createWalletClient({ + account: privateKeyToAccount( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', + ), // anvil index: 0 + chain: anvil, + transport: http(), +}) + +const anvilClient = createPublicClient({ + chain: anvil, + transport: http(), +}) + +export class DeploymentRoute extends Route { + public readonly name = 'deployments' as const + + public readonly triggerDeployment = 'triggerDeployment' as const + public readonly triggerDeploymentController = this.trpc.procedure + .input( + z.object({ + bytecode: this.z.string().refine(isHex, { + message: `Invalid bytecode hex`, + }), + abi: this.z.any(), + version: this.z.string(), + constructorArgs: this.z.any().array().optional(), + }), + ) + .mutation(async ({ input }) => { + const { abi, bytecode, constructorArgs } = input + + const data = encodeDeployData({ + abi, + bytecode, + args: constructorArgs ?? [], + }) + + const computedAddress = (await anvilClient.readContract({ + abi: create2FactoryAbi, + address: create2FactoryContractAddress, + functionName: 'computeAddress', + args: [data, DEPLOY_SALT], + })) as Address + + const code = await anvilClient.getBytecode({ address: computedAddress }) + if (code) { + throw Trpc.handleStatus( + 400, + `Contract with address ${computedAddress} already exists`, + ) + } + + const deployTxHash = await deployer.writeContract({ + abi: create2FactoryAbi, + address: create2FactoryContractAddress, + functionName: 'deploy', + args: [data, DEPLOY_SALT], + }) + + const receipt = await anvilClient.waitForTransactionReceipt({ + hash: deployTxHash, + }) + + return { + receipt, + deploymentHash: deployTxHash, + address: computedAddress, + } + }) + + public readonly handler = this.trpc.router({ + [this.triggerDeployment]: this.triggerDeploymentController, + }) +}