Saltar al contenido principal

Requisitos previos

  • Un contrato desplegado en la testnet de Plasma. Consulta despliega un contrato si aún no has desplegado uno.
  • El código fuente y la configuración de compilación de tu contrato.
  • Los argumentos del constructor utilizados durante el despliegue (si los hay).
El explorador de bloques de Plasma (plasmascan.to) ofrece una API de verificación compatible con Etherscan. Los ejemplos a continuación usan esa API directamente — no se requiere una cuenta de terceros para enviar una verificación.

Contratos de ejemplo

Usaremos dos contratos de ejemplo para demostrar argumentos de constructor tanto simples como complejos:

Contrato simple

Un contrato básico de almacenamiento de datos que demuestra conceptos fundamentales de smart contracts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract SimpleStorage {
    string private storedData;
    address public owner;
    
    constructor(string memory initialData) {
        storedData = initialData;
        owner = msg.sender;
    }
    
    function setData(string memory data) public {
        require(msg.sender == owner, "Only owner can set data");
        storedData = data;
    }
    
    function getData() public view returns (string memory) {
        return storedData;
    }
}

Contrato complejo

Un sistema de vault más sofisticado para administrar depósitos de tokens con parámetros configurables.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract TokenVault {
    struct VaultConfig {
        uint256 minDeposit;
        uint256 maxDeposit;
        bool isActive;
    }
    
    address[] public authorisedTokens;
    VaultConfig public config;
    mapping(address => uint256) public balances;
    
    constructor(
        address[] memory _tokens,
        uint256 _minDeposit,
        uint256 _maxDeposit,
        bool _isActive
    ) {
        authorisedTokens = _tokens;
        config = VaultConfig(_minDeposit, _maxDeposit, _isActive);
    }
    
    function deposit(address token, uint256 amount) external {
        require(config.isActive, "Vault is not active");
        require(amount >= config.minDeposit, "Amount below minimum");
        require(amount <= config.maxDeposit, "Amount above maximum");
        balances[msg.sender] += amount;
    }
}

Verifica con Foundry

Foundry provee el comando forge verify-contract para la verificación de contratos.
  1. Asegúrate de que tu foundry.toml incluya la configuración de verificación:
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc_version = "0.8.28"
    optimizer = true
    optimizer_runs = 200
    
    [rpc_endpoints]
    plasma_testnet = "https://testnet-rpc.plasma.to"
    
  2. Verifica el contrato. Para el contrato SimpleStorage con un argumento de constructor de tipo string:
    forge verify-contract \
      --chain-id 9746 \
      --num-of-optimizations 200 \
      --watch \
      --constructor-args $(cast abi-encode "constructor(string)" "Hello, Plasma!") \
      --compiler-version v0.8.28+commit.7893614a \
      0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
      src/SimpleStorage.sol:SimpleStorage
    
    Para el contrato TokenVault con múltiples argumentos de constructor:
    # First, encode the constructor arguments.
    CONSTRUCTOR_ARGS=$(cast abi-encode \
        "constructor(address[],uint256,uint256,bool)" \
        "[0x1234567890123456789012345678901234567890,0x0987654321098765432109876543210987654321]" \
        1000000000000000000 \
        10000000000000000000 \
        true)
    
    forge verify-contract \
        --chain-id 9746 \
        --num-of-optimizations 200 \
        --watch \
        --constructor-args $CONSTRUCTOR_ARGS \
        --compiler-version v0.8.28+commit.7893614a \
        0x9876543210987654321098765432109876543210 \
        src/TokenVault.sol:TokenVault
    
  3. Verifica el estado de la verificación. Foundry mostrará el estado de verificación en tiempo real con la bandera --watch. Busca:
    Submitting verification for [src/SimpleStorage.sol:SimpleStorage] 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F.
    Submitted contract for verification:
            Response: `OK`
            GUID: `abc123def456ghi789`
            URL: https://testnet.plasmascan.to/address/0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F
    Contract verification status:
    Response: `NOTOK`
    Details: `Pending in queue`
    Contract verification status:
    Response: `OK`
    Details: `Pass - Verified`
    Contract successfully verified
    

Verifica con Hardhat

Hardhat ofrece verificación de contratos a través del plugin @nomicfoundation/hardhat-verify.
  1. Actualiza tu hardhat.config.js para incluir la configuración de verificación:
    require("@nomicfoundation/hardhat-toolbox");
    require("@nomicfoundation/hardhat-verify");
    require("dotenv").config();
    
    module.exports = {
      solidity: {
        version: "0.8.28",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200
          }
        }
      },
      networks: {
        plasmaTestnet: {
          url: process.env.RPC_URL,
          chainId: 9746,
          accounts: [process.env.PRIVATE_KEY]
        }
      },
      etherscan: {
        customChains: [
          {
            network: "plasmaTestnet",
            chainId: 9746,
            urls: {
              apiURL: "https://testnet.plasmascan.to/api",
              browserURL: "https://testnet.plasmascan.to/"
            }
          }
        ]
      },
      sourcify: {
        enabled: false
      }
    };
    
  2. Verifica el contrato. Para el contrato simple:
    npx hardhat verify \
        --network plasmaTestnet \
        0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
        "Hello, Plasma!"
    
    Para contratos con múltiples argumentos de constructor, crea un script de verificación scripts/verify.js:
    const hre = require("hardhat");
    
    async function main() {
      const contractAddress = "0x9876543210987654321098765432109876543210";
      
      // Constructor arguments for TokenVault.
      const constructorArgs = [
        [
          "0x1234567890123456789012345678901234567890",
          "0x0987654321098765432109876543210987654321"
        ], // address[] _tokens
        "1000000000000000000", // uint256 _minDeposit (1e18, assuming 18 decimals)
        "10000000000000000000", // uint256 _maxDeposit (1e19, assuming 18 decimals)
        true // bool _isActive
      ];
    
      try {
        await hre.run("verify:verify", {
          address: contractAddress,
          constructorArguments: constructorArgs,
        });
        console.log("Contract verified successfully!");
      } catch (error) {
        console.error("Verification failed:", error);
      }
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    
  3. Ejecuta el script de verificación:
    npx hardhat run scripts/verify.js --network plasmaTestnet
    

Verificación automatizada durante el despliegue

También puedes verificar contratos automáticamente durante el despliegue agregando la verificación a tu script de despliegue:
const { ethers } = require("hardhat");

async function main() {
  const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
  const simpleStorage = await SimpleStorage.deploy("Hello, Plasma!");
  
  await simpleStorage.waitForDeployment();
  const contractAddress = await simpleStorage.getAddress();
  
  console.log("SimpleStorage deployed to:", contractAddress);
  
  // Wait for a few block confirmations before verifying.
  console.log("Waiting for block confirmations...");
  await simpleStorage.deploymentTransaction().wait(5);
  
  // Verify the contract.
  try {
    await hre.run("verify:verify", {
      address: contractAddress,
      constructorArguments: ["Hello, Plasma!"],
    });
    console.log("Contract verified successfully!");
  } catch (error) {
    console.log("Verification failed:", error.message);
  }
}

main();

Verifica con Ethers.js

Ethers.js requiere llamadas API manuales al explorador de bloques para la verificación. Este enfoque te brinda control total sobre el proceso de verificación.
  1. Instala las dependencias requeridas:
    npm install ethers dotenv axios form-data
    
  2. Crea el script de verificación verify-ethers.js:
    const axios = require('axios');
    const fs = require('fs');
    require('dotenv').config();
    
    async function verifyContract(contractAddress, sourceCode, contractName, constructorArgs = "") {
      const apiUrl = "https://testnet.plasmascan.to/api";
      
      const data = {
        module: 'contract',
        action: 'verifysourcecode',
        contractaddress: contractAddress,
        sourceCode: sourceCode,
        codeformat: 'solidity-single-file',
        contractname: contractName,
        compilerversion: 'v0.8.28+commit.7893614a',
        optimizationUsed: '1',
        runs: '200',
        constructorArguements: constructorArgs, // Note: API uses this spelling.
        evmversion: 'default',
        licenseType: '3' // MIT License
      };
    
      try {
        console.log('Submitting contract for verification...');
        const response = await axios.post(apiUrl, new URLSearchParams(data));
        
        if (response.data.status === '1') {
          const guid = response.data.result;
          console.log('Verification submitted successfully!');
          console.log('GUID:', guid);
          
          // Check verification status.
          await checkVerificationStatus(guid);
        } else {
          console.error('Verification submission failed:', response.data.result);
        }
      } catch (error) {
        console.error('Error submitting verification:', error.message);
      }
    }
    
    async function checkVerificationStatus(guid) {
      const apiUrl = "https://testnet.plasmascan.to/api";
      const maxAttempts = 30;
      let attempts = 0;
    
      while (attempts < maxAttempts) {
        try {
          const response = await axios.get(apiUrl, {
            params: {
              module: 'contract',
              action: 'checkverifystatus',
              guid: guid
            }
          });
    
          const status = response.data.status;
          const result = response.data.result;
    
          if (status === '1') {
            console.log('✅ Contract verified successfully!');
            console.log('Result:', result);
            break;
          } else if (result.includes('Fail')) {
            console.error('❌ Verification failed:', result);
            break;
          } else {
            console.log('⏳ Verification pending...');
            attempts++;
            
            if (attempts < maxAttempts) {
              await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10 seconds.
            }
          }
        } catch (error) {
          console.error('Error checking status:', error.message);
          break;
        }
      }
    
      if (attempts >= maxAttempts) {
        console.log('⚠️ Verification status check timed out. Please check manually.');
      }
    }
    
    // Verify SimpleStorage contract.
    async function verifySimpleStorage() {
      const contractAddress = "0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F";
      const sourceCode = fs.readFileSync('SimpleStorage.sol', 'utf8');
      const constructorArgs = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20506c61736d612100000000000000000000000000000000000000";
      
      await verifyContract(contractAddress, sourceCode, "SimpleStorage", constructorArgs);
    }
    
    // Verify TokenVault contract.
    async function verifyTokenVault() {
      const contractAddress = "0x9876543210987654321098765432109876543210";
      const sourceCode = fs.readFileSync('TokenVault.sol', 'utf8');
      
      // Complex constructor arguments (ABI encoded).
      const constructorArgs = "0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000123456789012345678901234567890123456789000000000000000000000000098765432109876543210987654321098765432100";
      
      await verifyContract(contractAddress, sourceCode, "TokenVault", constructorArgs);
    }
    
    // Run verification (uncomment the one you need).
    verifySimpleStorage();
    // verifyTokenVault();
    
  3. Codifica los argumentos del constructor. Para argumentos de constructor complejos, debes codificarlos en ABI. Puedes usar la herramienta cast de Foundry:
    # For SimpleStorage with string argument.
    cast abi-encode "constructor(string)" "Hello, Plasma!"
    
    # For TokenVault with complex arguments.
    cast abi-encode \
        "constructor(address[],uint256,uint256,bool)" \
        "[0x1234567890123456789012345678901234567890,0x0987654321098765432109876543210987654321]" \
        1000000000000000000 \
        10000000000000000000 \
        true
    
  4. Finalmente, ejecuta la verificación:
    node verify-ethers.js
    

Comprueba el estado de la verificación

Después de la verificación, puedes comprobar si fue exitosa:

En el explorador de bloques

  1. Visita el explorador de la testnet de Plasma.
  2. Busca la dirección de tu contrato.
  3. Busca un check verde junto a la pestaña «Contract».
  4. Haz clic en la pestaña «Contract» para ver el código fuente verificado.

Programáticamente

  1. Crea un script de verificación de estado check-verification.js:
    const axios = require('axios');
    require('dotenv').config();
    
    async function checkIfVerified(contractAddress) {
      const apiUrl = "https://testnet.plasmascan.to/api";
      
      try {
        const response = await axios.get(apiUrl, {
          params: {
            module: 'contract',
            action: 'getsourcecode',
            address: contractAddress,
          }
        });
    
        const result = response.data.result[0];
        
        if (result.SourceCode && result.SourceCode !== '') {
          console.log('✅ Contract is verified!');
          console.log('Contract Name:', result.ContractName);
          console.log('Compiler Version:', result.CompilerVersion);
          console.log('Optimisation Used:', result.OptimizationUsed);
          return true;
        } else {
          console.log('❌ Contract is not verified');
          return false;
        }
      } catch (error) {
        console.error('Error checking verification status:', error.message);
        return false;
      }
    }
    
    // Check your contract.
    checkIfVerified("0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F");
    

Métodos alternativos

Si bien los exploradores de bloques son el método de verificación más común, también puedes usar:

Sourcify

Sourcify provee verificación descentralizada de contratos:
# Install Sourcify CLI.
npm install -g @ethereum-sourcify/cli

# Verify contract.
sourcify verify \
    --network 9746 \
    --address 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
    --files contracts/SimpleStorage.sol

Tenderly

Tenderly ofrece verificación como parte de su plataforma de depuración. Consulta su documentación para más detalles.

Aplanado de contratos

Para contratos con imports o librerías, es posible que necesites aplanar tu contrato en un único archivo antes de la verificación. Herramientas populares incluyen: Para proyectos complejos con librerías externas, consulta la documentación de verificación de Hardhat para opciones de configuración avanzadas.

Solución de problemas

Desajuste en los argumentos del constructor

Error: Invalid constructor arguments provided
Verifica nuevamente que tus argumentos del constructor coincidan exactamente con los usados durante el despliegue. Usa cast abi-encode para verificar la codificación.

Desajuste en la versión del compilador

Error: Compilation failed
Asegúrate de que la versión del compilador en tu solicitud de verificación coincida con la versión usada durante la compilación.

Desajuste en la configuración de optimización

Error: Bytecode doesn't match
Verifica que la configuración de optimización (habilitada/deshabilitada y conteo de runs) coincida con tu configuración de compilación.

Ya verificado

Error: Contract source code already verified
Esto significa que la verificación fue exitosa previamente. Revisa el explorador de bloques para confirmar.

Límite de tasa

Error: Rate limit exceeded
Espera unos minutos antes de reintentar. Considera actualizar tu plan de API si necesitas límites más altos.