Skip to content

Commit

Permalink
Adds deployment endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
nitaliano committed Apr 5, 2024
1 parent 125498e commit 04f0e57
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 2 deletions.
5 changes: 3 additions & 2 deletions apps/dapp-console-api/src/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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, {})
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions apps/dapp-console-api/src/api/ApiV0.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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,
})

/**
Expand All @@ -42,6 +44,7 @@ export class ApiV0 extends Api {
protected readonly routes: {
authRoute: AuthRoute
walletsRoute: WalletsRoute
deploymentRoute: DeploymentRoute
},
) {
super(trpc)
Expand Down
153 changes: 153 additions & 0 deletions apps/dapp-console-api/src/routes/Deployments.ts
Original file line number Diff line number Diff line change
@@ -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,
})
}

0 comments on commit 04f0e57

Please sign in to comment.