hts/apps/migrant/lib/functions/deploy-contract/deploy-contract.ts

174 lines
4.6 KiB
TypeScript

const solc = require('solc')
import {
DeployContractParams,
DeployContractResult,
VerifyContractParams
} from '@/lib/functions/types'
import handleImports from '@/lib/functions/deploy-contract/handle-imports'
import { getChainById, getExplorerUrl } from '@/lib/viem-utils'
import ipfsUpload from '@/lib/functions/deploy-contract/ipfs-upload'
import { Chain, Hex, createWalletClient, encodeDeployData, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { track } from '@vercel/analytics/server'
const ALCHEMY_API_KEY = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY
export default async function deployContract({
chainId,
contractName,
sourceCode,
constructorArgs
}: DeployContractParams): Promise<DeployContractResult> {
const viemChain = getChainById(Number(chainId)) as Chain
const fileName = contractName.replace(/[\/\\:*?"<>|.\s]+$/g, '_') + '.sol'
// Prepare the sources object for the Solidity compiler
const handleImportsResult = await handleImports(sourceCode)
const sources = {
[fileName]: {
content: handleImportsResult?.sourceCode
},
...handleImportsResult?.sources
}
const sourcesKeys = Object.keys(sources)
// Loop over each source
for (const sourceKey of sourcesKeys) {
let sourceCode = sources[sourceKey].content
// Find all import statements in the source code
const importStatements = sourceCode.match(/import\s+["'][^"']+["'];/g) || []
// Loop over each import statement
for (const importStatement of importStatements) {
// Extract the file name from the import statement
const importPathMatch = importStatement.match(/["']([^"']+)["']/)
// If no import path is found, continue to the next statement
if (!importPathMatch) continue
// Extract the file name from the path
const importPath = importPathMatch[1]
const fileName = importPath.split('/').pop() || importPath
// Check if the file is already in the sources object
// if (sources[fileName]) continue;
// Replace the import statement with the new import statement
sourceCode = sourceCode.replace(importStatement, `import "${fileName}";`)
}
// Update the source content in your sources object
sources[sourceKey].content = sourceCode
}
// Compile the contract
const standardJsonInput = JSON.stringify({
language: 'Solidity',
sources,
settings: {
evmVersion: 'paris',
outputSelection: {
'*': {
'*': ['*']
}
},
optimizer: {
enabled: true,
runs: 200
}
}
})
const output = JSON.parse(solc.compile(standardJsonInput))
if (output.errors) {
// Filter out warnings
const errors = output.errors.filter(
(error: { severity: string }) => error.severity === 'error'
)
if (errors.length > 0) {
const error = new Error(errors[0].formattedMessage)
throw error
}
}
const contract = output.contracts[fileName]
// Get the contract ABI and bytecode
const abi = contract[contractName].abi
let bytecode = contract[contractName].evm.bytecode.object
if (!bytecode.startsWith('0x')) {
bytecode = '0x' + bytecode
}
const deployerPk: Hex = `0x${process.env.DEPLOYER_PRIVATE_KEY}`
const account = privateKeyToAccount(deployerPk)
const alchemyHttpUrl = viemChain?.rpcUrls?.alchemy?.http[0]
? `${viemChain.rpcUrls.alchemy.http[0]}/${ALCHEMY_API_KEY}`
: undefined
const walletClient = createWalletClient({
account,
chain: viemChain,
transport: http(alchemyHttpUrl)
})
if (!(await walletClient.getAddresses())) {
const error = new Error(`Wallet for chain ${viemChain.name} not available`)
console.log(error)
}
const deployData = encodeDeployData({
abi: abi,
bytecode: bytecode,
args: constructorArgs
})
const deployHash = await walletClient.deployContract({
abi: abi,
bytecode: bytecode,
account: account,
args: constructorArgs
})
const explorerUrl = `${getExplorerUrl(viemChain)}/tx/${deployHash}`
const ipfsCid = await ipfsUpload(
sources,
JSON.stringify(abi),
bytecode,
standardJsonInput
)
const ipfsUrl = `https://nftstorage.link/ipfs/${ipfsCid}`
const encodedConstructorArgs = deployData.slice(bytecode?.length)
const verifyContractConfig: VerifyContractParams = {
deployHash,
standardJsonInput,
encodedConstructorArgs,
fileName,
contractName,
viemChain
}
const deploymentData = {
sourceCode,
explorerUrl,
ipfsUrl,
verifyContractConfig,
abi,
standardJsonInput
}
track('deployed_contract', {
contractName,
explorerUrl
})
return deploymentData
}