231 lines
6.4 KiB
TypeScript
231 lines
6.4 KiB
TypeScript
import { VerifyContractParams } from '@/lib/functions/types'
|
|
import handleImports from '@/lib/functions/deploy-contract/handle-imports'
|
|
import { getExplorerUrl } from '@/lib/viem-utils'
|
|
import { encodeDeployData } from 'viem'
|
|
import { useNetwork, usePublicClient, useWalletClient } from 'wagmi'
|
|
import toast from 'react-hot-toast'
|
|
import { useGlobalStore } from '@/app/state/global-store'
|
|
import { track } from '@vercel/analytics'
|
|
|
|
export function useDeployWithWallet() {
|
|
const { chain: viemChain } = useNetwork()
|
|
const { data: walletClient } = useWalletClient()
|
|
const publicClient = usePublicClient({
|
|
chainId: viemChain?.id
|
|
})
|
|
const { setLastDeploymentData, setVerifyContractConfig } = useGlobalStore()
|
|
|
|
async function deploy({
|
|
contractName,
|
|
sourceCode,
|
|
constructorArgs
|
|
}: {
|
|
contractName: string
|
|
sourceCode: string
|
|
constructorArgs: Array<string>
|
|
}) {
|
|
if (!viemChain || !walletClient) {
|
|
throw new Error('Wallet not available')
|
|
}
|
|
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 compileContractResponse = await fetch('/api/compile-contract', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
standardJsonInput,
|
|
contractName
|
|
})
|
|
})
|
|
|
|
const compileResult = await compileContractResponse.json()
|
|
const { abi, bytecode } = compileResult
|
|
|
|
const parsedConstructorArgs = constructorArgs.map(arg => {
|
|
if (arg.startsWith('[') && arg.endsWith(']')) {
|
|
// Check if the string doesn't have double or single quotes after '[' and before ']'
|
|
if (arg.match(/(?<=\[)(?=[^"'])(.*)(?<=[^"'])(?=\])/g)) {
|
|
// Split the string by commas and remove the brackets
|
|
const elements = arg.slice(1, -1).split(',')
|
|
|
|
// Trim each element to remove extra spaces and return as an array
|
|
return elements.map(item => item.trim())
|
|
}
|
|
}
|
|
|
|
// Try parsing as JSON, or return the original argument
|
|
try {
|
|
return JSON.parse(arg)
|
|
} catch (error) {
|
|
return arg
|
|
}
|
|
})
|
|
|
|
const deployData = encodeDeployData({
|
|
abi: abi,
|
|
bytecode: bytecode,
|
|
args: parsedConstructorArgs
|
|
})
|
|
|
|
const [account] = await walletClient.getAddresses()
|
|
|
|
const deployHash = await toast.promise(
|
|
walletClient.deployContract({
|
|
abi: abi,
|
|
bytecode: bytecode,
|
|
account: account,
|
|
args: parsedConstructorArgs
|
|
}),
|
|
{
|
|
loading: 'Sending deploy transaction...',
|
|
success: 'Deploy transaction submitted!',
|
|
error: 'Failed to deploy contract'
|
|
}
|
|
)
|
|
|
|
const ipfsUploadResponse = await fetch('/api/ipfs-upload', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
sources,
|
|
abi,
|
|
bytecode,
|
|
standardJsonInput
|
|
})
|
|
})
|
|
|
|
const ipfsCid = await ipfsUploadResponse.json()
|
|
const ipfsUrl = `https://nftstorage.link/ipfs/${ipfsCid}`
|
|
|
|
const encodedConstructorArgs = deployData.slice(bytecode?.length)
|
|
|
|
const verifyContractConfig: VerifyContractParams = {
|
|
deployHash,
|
|
standardJsonInput,
|
|
encodedConstructorArgs,
|
|
fileName,
|
|
contractName,
|
|
viemChain
|
|
}
|
|
|
|
setVerifyContractConfig(verifyContractConfig)
|
|
|
|
const txHashExplorerUrl = getExplorerUrl(viemChain) + `/tx/${deployHash}`
|
|
|
|
track('deployed_contract', {
|
|
contractName,
|
|
explorerUrl: txHashExplorerUrl
|
|
})
|
|
|
|
try {
|
|
const transactionReceipt = await toast.promise(
|
|
publicClient.waitForTransactionReceipt({
|
|
hash: verifyContractConfig?.deployHash
|
|
}),
|
|
{
|
|
loading: 'Waiting for confirmations...',
|
|
success: 'Transaction confirmed!',
|
|
error: 'Failed to receive enough confirmations'
|
|
}
|
|
)
|
|
const address = transactionReceipt?.contractAddress || undefined
|
|
|
|
const deploymentData = {
|
|
address,
|
|
transactionHash: deployHash,
|
|
ipfsUrl,
|
|
explorerUrl: txHashExplorerUrl,
|
|
verificationStatus: 'pending',
|
|
standardJsonInput,
|
|
abi,
|
|
sourceCode
|
|
}
|
|
|
|
setLastDeploymentData(deploymentData)
|
|
return deploymentData
|
|
} catch (error) {
|
|
console.log(error)
|
|
const deploymentData = {
|
|
transactionHash: deployHash,
|
|
explorerUrl: txHashExplorerUrl,
|
|
ipfsUrl,
|
|
verificationStatus: 'pending',
|
|
standardJsonInput,
|
|
abi,
|
|
sourceCode
|
|
}
|
|
setLastDeploymentData(deploymentData)
|
|
return deploymentData
|
|
}
|
|
}
|
|
|
|
return { deploy }
|
|
}
|