Kaynağa Gözat

Chore(contract manger) lazer deploy script (#2971)

* chore(contract-manager) Lazer Deploy scripts

* update

* fix polygon rpc

* fix

* fix

* Added script for governance

* governance script (WIP)

* fix

* revert Script lazer

* fix

* added comment
Aditya Arora 3 ay önce
ebeveyn
işleme
f1c82b6859

+ 343 - 0
contract_manager/scripts/deploy_evm_lazer_contracts.ts

@@ -0,0 +1,343 @@
+/**
+ * PythLazer EVM Contract Deployment and Management Script
+ *
+ * This script provides functionality to deploy PythLazer contracts and manage trusted signers
+ * on EVM-compatible blockchains. It integrates with the DefaultStore system and supports
+ * both deployment and contract management operations.
+ *
+ * FLAGS AND USAGE:
+ *
+ * 1. DEPLOYMENT FLAGS:
+ *    --deploy                    Deploy the PythLazer contract (default: true)
+ *    --verify                    Verify contract on block explorer after deployment
+ *    --etherscan-api-key <key>   Required if --verify is true
+ *
+ * 2. TRUSTED SIGNER MANAGEMENT:
+ *    --update-signer <address>   Address of the trusted signer to add/update
+ *    --expires-at <timestamp>    Unix timestamp when the signer expires
+ *
+ * EXAMPLES:
+ *
+ * Deploy only:
+ *   npx ts-node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key>
+ *
+ * Deploy with verification:
+ *   npx ts-node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key> --verify --etherscan-api-key <key>
+ *
+ * Update trusted signer only (requires existing contract):
+ *   npx ts-node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key> --deploy false --update-signer 0x123... --expires-at 1735689600
+ *
+ * Deploy and update trusted signer in one command:
+ *   npx ts-node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key> --update-signer 0x123... --expires-at 1735689600
+ *
+ * NOTES:
+ * - The --deploy flag defaults to true if no other flags are specified
+ * - Both --update-signer and --expires-at must be provided together
+ * - If updating trusted signer without deploying, an existing contract must be found
+ * - The script automatically saves deployed contracts to the store and updates EvmLazerContracts.json
+ * - All operations use the chain's RPC URL from the DefaultStore
+ */
+
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { execSync } from "child_process";
+import { join } from "path";
+import { DefaultStore } from "../src/node/utils/store";
+import { EvmChain } from "../src/core/chains";
+import { EvmLazerContract } from "../src/core/contracts/evm";
+import { toPrivateKey, PrivateKey } from "../src/core/base";
+
+const parser = yargs(hideBin(process.argv))
+  .usage(
+    "Deploys PythLazer contracts and/or updates trusted signers\n" +
+      "Usage: $0 --chain <chain_name> --private-key <private_key> [--deploy] [--update-signer <address> --expires-at <timestamp>]",
+  )
+  .options({
+    chain: {
+      type: "string",
+      description: "Chain name to deploy to (from EvmChains.json)",
+      demandOption: true,
+    },
+    "private-key": {
+      type: "string",
+      description: "Private key for deployment and transactions",
+      demandOption: true,
+    },
+    deploy: {
+      type: "boolean",
+      description:
+        "Deploy the PythLazer contract (default: true if no other flags specified)",
+      default: true,
+    },
+    verify: {
+      type: "boolean",
+      description:
+        "Verify contract on block explorer (only used with --deploy)",
+      default: false,
+    },
+    "etherscan-api-key": {
+      type: "string",
+      description:
+        "Etherscan API key for verification (required if --verify is true)",
+    },
+    "update-signer": {
+      type: "string",
+      description: "Update trusted signer address (requires --expires-at)",
+    },
+    "expires-at": {
+      type: "number",
+      description:
+        "Expiration timestamp for trusted signer in Unix timestamp format (required if --update-signer is specified)",
+    },
+  })
+  .check((argv) => {
+    // If update-signer is specified, expires-at must also be specified
+    if (argv["update-signer"] && !argv["expires-at"]) {
+      throw new Error(
+        "--expires-at is required when --update-signer is specified",
+      );
+    }
+
+    // If expires-at is specified, update-signer must also be specified
+    if (argv["expires-at"] && !argv["update-signer"]) {
+      throw new Error(
+        "--update-signer is required when --expires-at is specified",
+      );
+    }
+
+    // If verify is true, etherscan-api-key must be provided
+    if (argv.verify && !argv["etherscan-api-key"]) {
+      throw new Error("--etherscan-api-key is required when --verify is true");
+    }
+
+    return true;
+  });
+
+/**
+ * Deploys the PythLazer contract using forge script
+ * @param chain The EVM chain to deploy to
+ * @param privateKey The private key for deployment
+ * @param verify Whether to verify the contract
+ * @param etherscanApiKey The Etherscan API key for verification
+ * @returns The deployed contract address
+ */
+async function deployPythLazerContract(
+  chain: EvmChain,
+  privateKey: string,
+  verify: boolean,
+  etherscanApiKey?: string,
+): Promise<string> {
+  const lazerContractsDir = join(__dirname, "../../lazer/contracts/evm");
+  const rpcUrl = chain.rpcUrl;
+
+  console.log(`Deploying PythLazer contract to ${chain.getId()}...`);
+  console.log(`RPC URL: ${rpcUrl}`);
+
+  // Build forge command
+  let forgeCommand = `forge script script/PythLazerDeploy.s.sol --rpc-url ${rpcUrl} --private-key ${privateKey} --broadcast`;
+
+  if (verify && etherscanApiKey) {
+    forgeCommand += ` --verify --etherscan-api-key ${etherscanApiKey}`;
+  }
+
+  try {
+    // Execute forge script
+    console.log("Running forge deployment script...");
+    const output = execSync(forgeCommand, {
+      cwd: lazerContractsDir,
+      encoding: "utf8",
+      stdio: "pipe",
+    });
+
+    console.log("Deployment output:");
+    console.log(output);
+
+    // Extract proxy address from output
+    const proxyMatch = output.match(/Deployed proxy to: (0x[a-fA-F0-9]{40})/);
+    if (!proxyMatch) {
+      throw new Error("Could not extract proxy address from deployment output");
+    }
+
+    const proxyAddress = proxyMatch[1];
+    console.log(`\nPythLazer proxy deployed at: ${proxyAddress}`);
+
+    return proxyAddress;
+  } catch (error) {
+    console.error("Deployment failed:", error);
+    throw error;
+  }
+}
+
+/**
+ * Updates the EvmLazerContracts.json file with the new deployment
+ * @param chain The chain where the contract was deployed
+ * @param address The deployed contract address
+ */
+function updateContractsFile(chain: EvmChain, address: string): void {
+  console.log(`Updating contracts file for ${chain.getId()}`);
+  const lazerContract = new EvmLazerContract(chain, address);
+  DefaultStore.lazer_contracts[lazerContract.getId()] = lazerContract;
+  DefaultStore.saveAllContracts();
+
+  console.log(`\nUpdated EvmLazerContracts.json with new deployment`);
+  console.log(`Chain: ${chain.getId()}`);
+  console.log(`Address: ${address}`);
+}
+
+/**
+ * Gets or creates an EvmLazerContract instance
+ * @param chain The EVM chain
+ * @param address The contract address
+ * @returns The EvmLazerContract instance
+ */
+function getOrCreateLazerContract(
+  chain: EvmChain,
+  address: string,
+): EvmLazerContract {
+  return new EvmLazerContract(chain, address);
+}
+
+/**
+ * Updates the trusted signer for a PythLazer contract
+ * @param chain The EVM chain
+ * @param contractAddress The contract address
+ * @param trustedSigner The trusted signer address
+ * @param expiresAt The expiration timestamp
+ * @param privateKey The private key for the transaction
+ */
+async function updateTrustedSigner(
+  chain: EvmChain,
+  contractAddress: string,
+  trustedSigner: string,
+  expiresAt: number,
+  privateKey: PrivateKey,
+): Promise<void> {
+  const contract = getOrCreateLazerContract(chain, contractAddress);
+  await contract.updateTrustedSigner(trustedSigner, expiresAt, privateKey);
+}
+
+function findLazerContract(chain: EvmChain): EvmLazerContract | undefined {
+  for (const contract of Object.values(DefaultStore.lazer_contracts)) {
+    if (
+      contract instanceof EvmLazerContract &&
+      contract.chain.getId() === chain.getId()
+    ) {
+      console.log(
+        `Found lazer contract for ${chain.getId()} at ${contract.address}`,
+      );
+      return contract;
+    }
+  }
+}
+
+export async function findOrDeployPythLazerContract(
+  chain: EvmChain,
+  privateKey: string,
+  verify: boolean,
+  etherscanApiKey?: string,
+): Promise<string> {
+  const lazerContract = findLazerContract(chain);
+  if (lazerContract) {
+    console.log(
+      `Found lazer contract for ${chain.getId()} at ${lazerContract.address}`,
+    );
+    return lazerContract.address;
+  }
+  const deployedAddress = await deployPythLazerContract(
+    chain,
+    privateKey,
+    verify,
+    etherscanApiKey,
+  );
+  console.log(
+    `✅ PythLazer contract deployed successfully at ${deployedAddress}`,
+  );
+  updateContractsFile(chain, deployedAddress);
+  return deployedAddress;
+}
+
+export async function main() {
+  const argv = await parser.argv;
+
+  // Get the chain from the store
+  const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain);
+
+  try {
+    let deployedAddress: string | undefined;
+
+    // Step 1: Deploy contract if requested
+    if (argv.deploy) {
+      console.log(`Deploying PythLazer contract to ${chain.getId()}...`);
+      console.log(`Chain: ${chain.getId()}`);
+      console.log(`RPC URL: ${chain.rpcUrl}`);
+      console.log(`Verification: ${argv.verify ? "Enabled" : "Disabled"}`);
+
+      deployedAddress = await findOrDeployPythLazerContract(
+        chain,
+        argv["private-key"],
+        argv.verify,
+        argv["etherscan-api-key"],
+      );
+    }
+
+    // Step 2: Update trusted signer if requested
+    if (argv["update-signer"] && argv["expires-at"]) {
+      console.log(`\nUpdating trusted signer on ${chain.getId()}...`);
+      console.log(`Signer Address: ${argv["update-signer"]}`);
+      console.log(
+        `Expires At: ${new Date(argv["expires-at"] * 1000).toISOString()}`,
+      );
+
+      let contractAddress: string;
+
+      // Use deployed address if we just deployed, otherwise find existing contract
+      if (deployedAddress) {
+        contractAddress = deployedAddress;
+        console.log(`Using newly deployed contract at ${contractAddress}`);
+      } else {
+        const lazerContract = findLazerContract(chain);
+        if (lazerContract) {
+          contractAddress = lazerContract.address;
+          console.log(`Using existing contract at ${contractAddress}`);
+        } else {
+          throw new Error(
+            `No lazer contract found for ${chain.getId()}. Deploy a contract first using --deploy.`,
+          );
+        }
+      }
+
+      await updateTrustedSigner(
+        chain,
+        contractAddress,
+        argv["update-signer"],
+        argv["expires-at"],
+        toPrivateKey(argv["private-key"]),
+      );
+
+      console.log(`\n✅ Trusted signer updated successfully`);
+    }
+
+    // Summary
+    console.log(`\n Operation Summary:`);
+    if (argv.deploy && argv["update-signer"]) {
+      console.log(`\n✅ Contract deployed at: ${deployedAddress}`);
+      console.log(`Trusted signer updated: ${argv["update-signer"]}`);
+      console.log(
+        `Expires at: ${new Date(argv["expires-at"]! * 1000).toISOString()}`,
+      );
+    } else if (argv.deploy) {
+      console.log(`Contract deployed at ${deployedAddress}`);
+    } else if (argv["update-signer"]) {
+      console.log(`Trusted signer updated successfully`);
+    } else {
+      console.log(
+        `No operations performed. Use --deploy to deploy or --update-signer to update trusted signer.`,
+      );
+    }
+  } catch (error) {
+    console.error("Operation failed:", error);
+    process.exit(1);
+  }
+}
+
+main();

+ 107 - 0
contract_manager/src/core/contracts/evm.ts

@@ -11,6 +11,7 @@ import {
   EXTENDED_PYTH_ABI,
   WORMHOLE_ABI,
   PULSE_UPGRADEABLE_ABI,
+  LAZER_ABI,
 } from "./evm_abis";
 
 /**
@@ -926,3 +927,109 @@ export class EvmPulseContract extends Storable {
     );
   }
 }
+
+export class EvmLazerContract extends Storable {
+  static type = "EvmLazerContract";
+
+  constructor(
+    public chain: EvmChain,
+    public address: string,
+  ) {
+    super();
+  }
+
+  getId(): string {
+    return `${this.chain.getId()}_${this.address}`;
+  }
+
+  getType(): string {
+    return EvmLazerContract.type;
+  }
+
+  toJson() {
+    return {
+      chain: this.chain.getId(),
+      address: this.address,
+      type: EvmLazerContract.type,
+    };
+  }
+
+  static fromJson(
+    chain: Chain,
+    parsed: { type: string; address: string },
+  ): EvmLazerContract {
+    if (parsed.type !== EvmLazerContract.type) {
+      throw new Error("Invalid type");
+    }
+    return new EvmLazerContract(chain as EvmChain, parsed.address);
+  }
+
+  getContract() {
+    const web3 = this.chain.getWeb3();
+    return new web3.eth.Contract(LAZER_ABI, this.address);
+  }
+
+  async getVersion(): Promise<string> {
+    const contract = this.getContract();
+    return await contract.methods.version().call();
+  }
+
+  async getOwner(): Promise<string> {
+    const contract = this.getContract();
+    return contract.methods.owner().call();
+  }
+
+  async generateUpdateTrustedSignerPayload(
+    trustedSigner: string,
+    expiresAt: number,
+  ): Promise<Buffer> {
+    // Executor contract is the owner of the PythLazer contract
+    const executorAddress = await this.getOwner();
+    const web3 = this.chain.getWeb3();
+    const executorContract = new web3.eth.Contract(
+      EXECUTOR_ABI,
+      executorAddress,
+    );
+    const data = executorContract.methods
+      .updateTrustedSigner(trustedSigner, expiresAt)
+      .encodeABI();
+    return this.chain.generateExecutorPayload(
+      executorAddress,
+      this.address,
+      data,
+    );
+  }
+
+  /**
+   * Updates the trusted signer for the PythLazer contract
+   * @param trustedSigner The address of the trusted signer
+   * @param expiresAt The expiration timestamp for the signer
+   * @param privateKey The private key to sign the transaction
+   * @note The privateKey should be the owner of the Lazer contract. It's not possible to run this function if the executor contract is the owner.
+   */
+  async updateTrustedSigner(
+    trustedSigner: string,
+    expiresAt: number,
+    privateKey: PrivateKey,
+  ): Promise<void> {
+    const web3 = this.chain.getWeb3();
+    const contract = this.getContract();
+
+    const account = web3.eth.accounts.privateKeyToAccount(privateKey);
+    const gasEstimate = await contract.methods
+      .updateTrustedSigner(trustedSigner, expiresAt)
+      .estimateGas({ from: account.address });
+
+    const tx = await contract.methods
+      .updateTrustedSigner(trustedSigner, expiresAt)
+      .send({
+        from: account.address,
+        gas: Math.floor(gasEstimate * 1.2), // 20% buffer
+      });
+
+    console.log(
+      `✅ Updated trusted signer ${trustedSigner} with expiration ${expiresAt}`,
+    );
+    console.log(`Transaction hash: ${tx.transactionHash}`);
+  }
+}

+ 192 - 0
contract_manager/src/core/contracts/evm_abis.ts

@@ -1366,3 +1366,195 @@ export const PULSE_UPGRADEABLE_ABI = [
     ],
   },
 ] as any; // eslint-disable-line  @typescript-eslint/no-explicit-any
+
+export const LAZER_ABI = [
+  { type: "constructor", inputs: [], stateMutability: "nonpayable" },
+  {
+    type: "function",
+    name: "UPGRADE_INTERFACE_VERSION",
+    inputs: [],
+    outputs: [{ name: "", type: "string", internalType: "string" }],
+    stateMutability: "view",
+  },
+  {
+    type: "function",
+    name: "initialize",
+    inputs: [
+      {
+        name: "_topAuthority",
+        type: "address",
+        internalType: "address",
+      },
+    ],
+    outputs: [],
+    stateMutability: "nonpayable",
+  },
+  {
+    type: "function",
+    name: "isValidSigner",
+    inputs: [{ name: "signer", type: "address", internalType: "address" }],
+    outputs: [{ name: "", type: "bool", internalType: "bool" }],
+    stateMutability: "view",
+  },
+  {
+    type: "function",
+    name: "owner",
+    inputs: [],
+    outputs: [{ name: "", type: "address", internalType: "address" }],
+    stateMutability: "view",
+  },
+  {
+    type: "function",
+    name: "proxiableUUID",
+    inputs: [],
+    outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
+    stateMutability: "view",
+  },
+  {
+    type: "function",
+    name: "renounceOwnership",
+    inputs: [],
+    outputs: [],
+    stateMutability: "nonpayable",
+  },
+  {
+    type: "function",
+    name: "transferOwnership",
+    inputs: [{ name: "newOwner", type: "address", internalType: "address" }],
+    outputs: [],
+    stateMutability: "nonpayable",
+  },
+  {
+    type: "function",
+    name: "updateTrustedSigner",
+    inputs: [
+      {
+        name: "trustedSigner",
+        type: "address",
+        internalType: "address",
+      },
+      { name: "expiresAt", type: "uint256", internalType: "uint256" },
+    ],
+    outputs: [],
+    stateMutability: "nonpayable",
+  },
+  {
+    type: "function",
+    name: "upgradeToAndCall",
+    inputs: [
+      {
+        name: "newImplementation",
+        type: "address",
+        internalType: "address",
+      },
+      { name: "data", type: "bytes", internalType: "bytes" },
+    ],
+    outputs: [],
+    stateMutability: "payable",
+  },
+  {
+    type: "function",
+    name: "verification_fee",
+    inputs: [],
+    outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
+    stateMutability: "view",
+  },
+  {
+    type: "function",
+    name: "verifyUpdate",
+    inputs: [{ name: "update", type: "bytes", internalType: "bytes" }],
+    outputs: [
+      { name: "payload", type: "bytes", internalType: "bytes" },
+      { name: "signer", type: "address", internalType: "address" },
+    ],
+    stateMutability: "payable",
+  },
+  {
+    type: "function",
+    name: "version",
+    inputs: [],
+    outputs: [{ name: "", type: "string", internalType: "string" }],
+    stateMutability: "pure",
+  },
+  {
+    type: "event",
+    name: "Initialized",
+    inputs: [
+      {
+        name: "version",
+        type: "uint64",
+        indexed: false,
+        internalType: "uint64",
+      },
+    ],
+    anonymous: false,
+  },
+  {
+    type: "event",
+    name: "OwnershipTransferred",
+    inputs: [
+      {
+        name: "previousOwner",
+        type: "address",
+        indexed: true,
+        internalType: "address",
+      },
+      {
+        name: "newOwner",
+        type: "address",
+        indexed: true,
+        internalType: "address",
+      },
+    ],
+    anonymous: false,
+  },
+  {
+    type: "event",
+    name: "Upgraded",
+    inputs: [
+      {
+        name: "implementation",
+        type: "address",
+        indexed: true,
+        internalType: "address",
+      },
+    ],
+    anonymous: false,
+  },
+  {
+    type: "error",
+    name: "AddressEmptyCode",
+    inputs: [{ name: "target", type: "address", internalType: "address" }],
+  },
+  {
+    type: "error",
+    name: "ERC1967InvalidImplementation",
+    inputs: [
+      {
+        name: "implementation",
+        type: "address",
+        internalType: "address",
+      },
+    ],
+  },
+  { type: "error", name: "ERC1967NonPayable", inputs: [] },
+  { type: "error", name: "FailedCall", inputs: [] },
+  { type: "error", name: "InvalidInitialization", inputs: [] },
+  { type: "error", name: "NotInitializing", inputs: [] },
+  {
+    type: "error",
+    name: "OwnableInvalidOwner",
+    inputs: [{ name: "owner", type: "address", internalType: "address" }],
+  },
+  {
+    type: "error",
+    name: "OwnableUnauthorizedAccount",
+    inputs: [{ name: "account", type: "address", internalType: "address" }],
+  },
+  { type: "error", name: "UUPSUnauthorizedCallContext", inputs: [] },
+  {
+    type: "error",
+    name: "UUPSUnsupportedProxiableUUID",
+    inputs: [{ name: "slot", type: "bytes32", internalType: "bytes32" }],
+  },
+] as any; // eslint-disable-line  @typescript-eslint/no-explicit-any

+ 8 - 1
contract_manager/src/node/utils/store.ts

@@ -30,6 +30,7 @@ import {
   IotaPriceFeedContract,
   EvmPulseContract,
   EvmExecutorContract,
+  EvmLazerContract,
 } from "../../core/contracts";
 import { Token } from "../../core/token";
 import { PriceFeedContract, Storable } from "../../core/base";
@@ -53,6 +54,7 @@ export class Store {
   public wormhole_contracts: Record<string, WormholeContract> = {};
   public tokens: Record<string, Token> = {};
   public vaults: Record<string, Vault> = {};
+  public lazer_contracts: Record<string, EvmLazerContract> = {};
 
   constructor(public path: string) {
     this.loadAllChains();
@@ -121,6 +123,7 @@ export class Store {
     contracts.push(...Object.values(this.entropy_contracts));
     contracts.push(...Object.values(this.wormhole_contracts));
     contracts.push(...Object.values(this.executor_contracts));
+    contracts.push(...Object.values(this.lazer_contracts));
     for (const contract of contracts) {
       if (!contractsByType[contract.getType()]) {
         contractsByType[contract.getType()] = [];
@@ -181,6 +184,7 @@ export class Store {
       [NearWormholeContract.type]: NearWormholeContract,
       [IotaPriceFeedContract.type]: IotaPriceFeedContract,
       [IotaWormholeContract.type]: IotaWormholeContract,
+      [EvmLazerContract.type]: EvmLazerContract,
     };
     this.getJsonFiles(`${this.path}/contracts/`).forEach((jsonFile) => {
       const parsedArray = JSON.parse(readFileSync(jsonFile, "utf-8"));
@@ -197,7 +201,8 @@ export class Store {
           this.contracts[chainContract.getId()] ||
           this.entropy_contracts[chainContract.getId()] ||
           this.wormhole_contracts[chainContract.getId()] ||
-          this.executor_contracts[chainContract.getId()]
+          this.executor_contracts[chainContract.getId()] ||
+          this.lazer_contracts[chainContract.getId()]
         )
           throw new Error(
             `Multiple contracts with id ${chainContract.getId()} found`,
@@ -208,6 +213,8 @@ export class Store {
           this.wormhole_contracts[chainContract.getId()] = chainContract;
         } else if (chainContract instanceof EvmExecutorContract) {
           this.executor_contracts[chainContract.getId()] = chainContract;
+        } else if (chainContract instanceof EvmLazerContract) {
+          this.lazer_contracts[chainContract.getId()] = chainContract;
         } else {
           this.contracts[chainContract.getId()] = chainContract;
         }

+ 62 - 0
contract_manager/store/contracts/EvmLazerContracts.json

@@ -0,0 +1,62 @@
+[
+  {
+    "chain": "sepolia",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "ethereal_testnet",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "ethereal_testnet_v2",
+    "address": "0x4D4772F06c595F69FB57039599a180536FDE8245",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "soneium_minato_testnet",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "sonic_blaze_testnet",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "optimism_sepolia",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "soneium",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "base_sepolia",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "base",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "polynomial_testnet",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "polynomial",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
+  },
+  {
+    "chain": "ethereal_devnet",
+    "address": "0x4D4772F06c595F69FB57039599a180536FDE8245",
+    "type": "EvmLazerContract"
+  }
+]