Skip to main content
Glama
deployERC721.ts17.1 kB
import { getWalletClient, getPublicClient } from './clients.js'; import { getPrivateKeyAsHex, getSeiTraceApiKey } from '../config.js'; import { DEFAULT_NETWORK } from '../chains.js'; import { getContract, encodeAbiParameters } from 'viem'; import { erc721Abi, ERC721_BYTECODE } from './abi/erc721.js'; import { VERIFICATION_APIS, ERC721_SOURCE_CODE } from './utils.js'; import { helpers } from './index.js'; /** * Deploys a custom ERC721 token contract. * @param name The name of the token. * @param symbol The symbol of the token. * @param baseURI_ The base URI for token metadata. * @param network The network to deploy to. * @returns The transaction hash of the deployment and the deployer's address. */ export async function deployERC721( name: string, symbol: string, baseURI_: string, network = DEFAULT_NETWORK ): Promise<{ txHash: `0x${string}`, deployerAddress: `0x${string}` }> { try { const privateKey = getPrivateKeyAsHex(); if (!privateKey) { throw new Error('Private key not available. Set the PRIVATE_KEY environment variable.'); } const walletClient = getWalletClient(privateKey, network); const account = walletClient.account; console.log(`Deploying ERC721 token "${name}" (${symbol}) with base URI "${baseURI_}"...`); const deploymentArgs = { abi: erc721Abi, bytecode: ERC721_BYTECODE as `0x${string}`, args: [name, symbol, baseURI_] as const, account, chain: walletClient.chain, gas: 2000000n, }; const hash = await walletClient.deployContract({ ...deploymentArgs, account: deploymentArgs.account || null }); console.log(`ERC721 contract deployment transaction sent. Hash: ${hash}`); return { txHash: hash, deployerAddress: account?.address || '0x0' }; } catch (error) { console.error('Deployment error details:', error); if (error instanceof Error) { if (error.message.includes('insufficient funds')) { throw new Error(`Insufficient funds for deployment. Check your account balance and gas requirements.`); } else if (error.message.includes('nonce')) { throw new Error(`Nonce error. Try again in a few seconds.`); } else if (error.message.includes('gas')) { throw new Error(`Gas-related error: ${error.message}. Try increasing gas limit or check network status.`); } throw new Error(`Failed to deploy ERC721 contract: ${error.message}`); } throw new Error(`Failed to deploy ERC721 contract: ${String(error)}`); } } /** * Verifies an ERC721 contract locally. * @param contractAddress The deployed contract address. * @param name The name of the token used during deployment. * @param symbol The symbol of the token used during deployment. * @param baseURI_ The base URI used during deployment. * @param network The network where the contract was deployed. * @returns Verification result with status and details. */ export async function verifyERC721ContractLocal( contractAddress: string, name: string, symbol: string, baseURI_: string, network = DEFAULT_NETWORK ): Promise<{ success: boolean, message: string, explorerUrl?: string, contractAddress: string }> { try { // Validate contract address format if (!contractAddress.startsWith('0x') || contractAddress.length !== 42) { throw new Error('Invalid contract address format'); } const privateKey = getPrivateKeyAsHex(); if (!privateKey) { throw new Error('Private key not available. Set the PRIVATE_KEY environment variable.'); } const publicClient = getPublicClient(network); const walletClient = getWalletClient(privateKey, network); console.log(`Verifying ERC721 contract at ${contractAddress} on ${network}...`); // First, verify the contract exists and has code const code = await publicClient.getCode({ address: contractAddress as `0x${string}` }); if (!code || code === '0x') { throw new Error('No contract found at the specified address'); } // Basic contract validation by checking if it responds to ERC721 standard calls try { const contract = getContract({ address: contractAddress as `0x${string}`, abi: erc721Abi, client: publicClient }); // Read contract data to verify it matches deployment parameters const [contractName, contractSymbol, contractBaseURI] = await Promise.all([ contract.read.name(), contract.read.symbol(), (contract.read as any).baseURI ? (contract.read as any).baseURI() : Promise.resolve(''), ]); const parameterMatches = { name: contractName === name, symbol: contractSymbol === symbol, baseURI_: contractBaseURI === baseURI_ }; const allMatch = Object.values(parameterMatches).every(match => match); if (!allMatch) { const mismatches = Object.entries(parameterMatches) .filter(([_, matches]) => !matches) .map(([param]) => param); console.warn(`Parameter mismatches detected: ${mismatches.join(', ')}`); return { success: false, message: `Contract verification failed: Parameter mismatches detected for ${mismatches.join(', ')}. Contract exists but parameters don't match deployment inputs.`, contractAddress, explorerUrl: walletClient.chain?.blockExplorers?.default ? `${walletClient.chain.blockExplorers.default.url}/address/${contractAddress}` : undefined }; } // Contract verification successful const explorerUrl = walletClient.chain?.blockExplorers?.default ? `${walletClient.chain.blockExplorers.default.url}/address/${contractAddress}` : undefined; console.log(`✅ Contract verification successful!`); console.log(`Contract Address: ${contractAddress}`); console.log(`Name: ${contractName}`); console.log(`Symbol: ${contractSymbol}`); console.log(`Base URI: ${contractBaseURI}`); if (explorerUrl) { console.log(`Explorer URL: ${explorerUrl}`); } return { success: true, message: `Contract verification successful! All parameters match deployment inputs.`, contractAddress, explorerUrl }; } catch (contractError) { throw new Error(`Contract interaction failed: ${contractError instanceof Error ? contractError.message : String(contractError)}`); } } catch (error) { console.error('Verification error details:', error); if (error instanceof Error) { if (error.message.includes('network')) { throw new Error(`Network error during verification: ${error.message}`); } else if (error.message.includes('address')) { throw new Error(`Invalid contract address: ${error.message}`); } throw new Error(`Contract verification failed: ${error.message}`); } throw new Error(`Contract verification failed: ${String(error)}`); } } /** * Verifies an ERC721 contract on a block explorer by submitting source code. * @param contractAddress The deployed contract address. * @param name The name of the token used during deployment. * @param symbol The symbol of the token used during deployment. * @param baseURI_ The base URI used during deployment. * @param network The network where the contract was deployed. * @returns Verification result with status and details. */ export async function verifyERC721Contract( contractAddress: string, name: string, symbol: string, baseURI_: string, network = DEFAULT_NETWORK ): Promise<{ success: boolean, message: string, explorerUrl?: string, contractAddress: string, verificationGuid?: string }> { try { // Validate contract address format if (!contractAddress.startsWith('0x') || contractAddress.length !== 42) { throw new Error('Invalid contract address format'); } const networkConfig = VERIFICATION_APIS[network as keyof typeof VERIFICATION_APIS]; if (!networkConfig) { throw new Error(`Verification not supported for network: ${network}. Supported networks: ${Object.keys(VERIFICATION_APIS).join(', ')}`); } const privateKey = getPrivateKeyAsHex(); if (!privateKey) { throw new Error('Private key not available. Set the PRIVATE_KEY environment variable.'); } const walletClient = getWalletClient(privateKey, network); const chainConfig = walletClient.chain; if (!chainConfig) { throw new Error(`Unsupported network: ${network}`); } console.log(`Verifying ERC721 contract at ${contractAddress} on the block explorer...`); const seiscanApiKey = getSeiTraceApiKey(); if (!seiscanApiKey) { throw new Error('Block explorer API key not available. Set the SEITRACE_API_KEY environment variable.'); } const publicClient = getPublicClient(network); const code = await publicClient.getBytecode({ address: contractAddress as `0x${string}` }); if (!code || code === '0x') { throw new Error('No contract found at the specified address'); } const constructorArgs = await encodeERC721ConstructorArguments(name, symbol, baseURI_); const verificationPayload = { module: 'contract', action: 'verifysourcecode', apikey: seiscanApiKey, contractaddress: contractAddress.toLowerCase(), sourceCode: ERC721_SOURCE_CODE, codeformat: 'solidity-single-file', contractname: 'BasicERC721', compilerversion: 'v0.8.20+commit.a1b79de6', optimizationUsed: '1', runs: '200', constructorArguements: constructorArgs, evmversion: 'paris', licenseType: '3' }; console.log('Submitting contract for verification...'); console.log(`API Endpoint: ${networkConfig.apiUrl}`); console.log(`Contract Address: ${contractAddress}`); console.log(`Constructor Args: ${constructorArgs}`); const response = await fetch(networkConfig.url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'ERC721-Deployment-Tool/1.0' }, body: new URLSearchParams(verificationPayload).toString() }); if (!response.ok) { const errorText = await response.text(); console.error('API Response Error:', errorText); throw new Error(`Verification API request failed: ${response.status} - ${errorText}`); } const result = await response.json(); console.log('Verification API Response:', result); if (result.status === '1' || result.message === 'OK') { const explorerUrl = `${networkConfig.explorerUrl}/address/${contractAddress}`; console.log('✅ Contract verification submitted successfully!'); console.log(`Contract Address: ${contractAddress}`); console.log(`Explorer URL: ${explorerUrl}`); console.log(`Verification GUID: ${result.result || 'N/A'}`); return { success: true, message: 'Contract verification submitted successfully! It may take a few minutes to process.', contractAddress, explorerUrl, verificationGuid: result.result }; } else if (result.status === '0') { if (result.result && result.result.includes('already verified')) { return { success: true, message: 'Contract is already verified on the block explorer.', contractAddress, explorerUrl: `${networkConfig.explorerUrl}/address/${contractAddress}` }; } else { throw new Error(`Verification failed: ${result.result || result.message || 'Unknown error'}`); } } else { throw new Error(`Unexpected response format: ${JSON.stringify(result)}`); } } catch (error) { console.error('Verification error details:', error); if (error instanceof Error) { if (error.message.includes('fetch')) { throw new Error(`Network error during verification: ${error.message}. Check if the block explorer API is accessible.`); } else if (error.message.includes('address')) { throw new Error(`Invalid contract address: ${error.message}`); } else if (error.message.includes('already verified')) { return { success: true, message: error.message, contractAddress, explorerUrl: VERIFICATION_APIS[network as keyof typeof VERIFICATION_APIS]?.explorerUrl ? `${VERIFICATION_APIS[network as keyof typeof VERIFICATION_APIS].explorerUrl}/address/${contractAddress}` : undefined }; } throw new Error(`Contract verification failed: ${error.message}`); } throw new Error(`Contract verification failed: ${String(error)}`); } } /** * Encodes constructor arguments for contract verification. * @param name Token name * @param symbol Token symbol * @param baseURI_ The base URI for token metadata. * @returns Encoded constructor arguments as hex string */ export function encodeERC721ConstructorArguments( name: string, symbol: string, baseURI_: string ): string { const encoded = encodeAbiParameters( [ { type: 'string', name: 'name' }, { type: 'string', name: 'symbol' }, { type: 'string', name: 'baseURI_' } ], [name, symbol, baseURI_] ); return encoded.slice(2); // remove `0x` prefix as required by verification APIs } /** * Mints the next available NFT to a specified address. * This function calls the `mint(address)` function on the contract. * @param nftContractAddress The address of the deployed ERC721 contract (NFT Contract Address). * @param toAddress The recipient address to receive the new NFT. * @param network The network where the contract is deployed. * @returns The transaction hash of the mint operation. */ export async function mintNFT( nftContractAddress: string, toAddress: string, network = DEFAULT_NETWORK ): Promise<{ txHash: `0x${string}` }> { try { const privateKey = getPrivateKeyAsHex(); if (!privateKey) { throw new Error('Private key not available.'); } const validatedNFTAddress = helpers.validateAddress(nftContractAddress); const walletClient = getWalletClient(privateKey, network); const account = walletClient.account; if (!account) { throw new Error('Could not get account from wallet client.'); } let validatedToAddress; if (toAddress === "") { validatedToAddress = account.address; } else { validatedToAddress = helpers.validateAddress(toAddress); } console.log(`Preparing to mint next available NFT to ${validatedToAddress}...`); const hash = await walletClient.writeContract({ address: validatedNFTAddress as `0x${string}`, abi: erc721Abi, functionName: 'mint', args: [validatedToAddress as `0x${string}`], account, chain: walletClient.chain }); console.log(`✅ NFT Mint transaction sent successfully! Hash: ${hash}`); return { txHash: hash }; } catch (error) { console.error('Minting error details:', error); throw new Error(`Failed to mint NFT: ${error instanceof Error ? error.message : String(error)}`); } } /** * Mints a batch of new NFTs to a specified address. * @param nftContractAddress The address of the deployed ERC721 contract (NFT Contract Address). * @param toAddress The recipient address to receive the new NFTs. * @param quantity The number of NFTs to mint in the batch. * @param network The network where the contract is deployed. * @returns The transaction hash of the mint operation. */ export async function mintBatchNFTs( nftContractAddress: string, toAddress: string, quantity: number, network = DEFAULT_NETWORK ): Promise<{ txHash: `0x${string}` }> { try { const privateKey = getPrivateKeyAsHex(); if (!privateKey) { throw new Error('Private key not available.'); } if (quantity <= 0) { throw new Error("Quantity must be greater than zero."); } const validatedNFTAddress = helpers.validateAddress(nftContractAddress); const walletClient = getWalletClient(privateKey, network); const account = walletClient.account; if (!account) { throw new Error('Could not get account from wallet client.'); } let validatedToAddress; if (toAddress === "") { validatedToAddress = account.address; } else { validatedToAddress = helpers.validateAddress(toAddress); } console.log(`Preparing to mint a batch of ${quantity} NFTs to ${validatedToAddress}...`); const hash = await walletClient.writeContract({ address: validatedNFTAddress as `0x${string}`, abi: erc721Abi, functionName: 'mintBatch', args: [validatedToAddress as `0x${string}`, BigInt(quantity)], account, chain: walletClient.chain }); console.log(`✅ NFT Batch Mint transaction sent successfully! Hash: ${hash}`); return { txHash: hash }; } catch (error) { console.error('Batch minting error details:', error); throw new Error(`Failed to mint batch of NFTs: ${error instanceof Error ? error.message : String(error)}`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/testinguser1111111/sei-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server