# How to Deploy an Application With the Same Address Cross-Chain

Deploying on multiple chains? Having a single address cross-chain makes it easier for your users and developers.

As the complexity of Web3 continues to grow in the cross-chain era, developers need to minimize obstacles for users. One easy-to-avoid obstacle is having an unnecessary number of addresses for your application across multiple blockchains. Having a single address for your application across chains is much easier for users and developers to interact with.

## **Tldr;**

We will look at three different approaches to deploying an application using the same address across multiple chains.

1. Use [Axelar Local Dev](https://www.npmjs.com/package/@axelar-network/axelar-local-dev) via a set deployer address at a specific [nonce](https://ethereum.stackexchange.com/questions/27432/what-is-nonce-in-ethereum-how-does-it-prevent-double-spending).
    
    1. **Pros:**
        
        1. Easily deploy your contract on two chains without needing to interact with any additional service.
            
    2. **Cons:**
        
        1. Can be cumbersome to coordinate current nonce on many different chains.
            
        2. No out of the box capability to precompute the address before deployment.
            
2. Use the Axelar [Create2 Deployer](https://docs.axelar.dev/dev/general-message-passing/solidity-utilities#create2-deployer) with the CREATE2 opcode.
    
    1. **Pros:**
        
        1. Can easily precompute address before deployment;
            
        2. Allows for more customization of the address of the contract by passing your own salt rather than being limited to the account’s nonce.
            
    2. **Cons:**
        
        1. Passing in different values to a constructor on different blockchains will lead to different addresses being deployed. As a result devs often end up using an `initializer()` function to simulate a constructor.
            
3. Use the Axelar [Create3 Deployer](https://docs.axelar.dev/dev/general-message-passing/solidity-utilities#create3-deployer).
    
    1. **Pros:**
        
        1. Can easily precompute address before deployment.
            
        2. Allows further customization of the contract asset by passing your own salt rather than being limited to the account’s nonce.
            
        3. Can compute an address independent of the contract’s bytecode, allowing you to pass in different values to a contract’s constructor and still deploy at the same address across multiple blockchains.
            

## **Part 1: Nonce-matching with Axelar Local Dev**

On blockchains running the [Ethereum Virtual Machine](https://ethereum.org/en/developers/docs/evm/) (EVM), addresses might appear to be randomly generated hex characters. In fact, they are deterministic. Generating an address in the EVM for a smart contract involves hashing the deployer’s address along with its current nonce using the [Keccak-256](https://www.linkedin.com/pulse/understanding-keccak256-cryptographic-hash-function-soares-m-sc-/) hashing algorithm. The resulting hash is truncated to obtain the contract address. For further reading on this process feel free to read through this [thread](https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed).

Let’s first look at deploying via specified nonce.

A nonce is a “number used once." In the context of an externally owned account, it represents the number of transactions sent from that given address. You should be able to generate the same address for your contract on multiple different blockchains, as long as you satisfy two conditions:

1. Deploy the contract from the same address.
    
2. Ensure that address is at the same nonce count on the various different blockchains you are deploying to.
    

The easiest way to make sure you can deploy from the same address on different blockchains is by using a [hierarchical deterministic (HD) wallet](https://kbaiiitmk.medium.com/hierarchical-deterministic-hd-wallet-4b883faab955), such as MetaMask. Let’s now step into the code!

This project will be using [Hardhat](https://hardhat.org/) to deploy your smart contract at the same address on the Polygon and Avalanche testnets.

To get started, simply run:

```bash
npx hardhat run init
```

When setting up the Hardhat project, you can choose either JavaScript or TypeScript, then select yes to add a `.gitignore` file and for installing required dependencies. You will be using TypeScript but feel free to use JavaScript if you prefer.

Once you have gone through the setup steps in your CLI, you should see a fresh Hardhat project in your code editor.

Out of the box, Hardhat gives you a simple contract called *Lock* and a script called `deploy.ts.`

First, you want to set up your `chains.json` file, which will contain relevant parameters for the blockchains you want to deploy to.

Set the `chains.json` file in the root of your project and add the following.

```bash
[
 {
    "name": "Polygon",
    "chainId": 80001,
    "gateway": "0xBF62ef1486468a6bd26Dd669C06db43dEd5B849B",
    "rpc": "<https://polygon-mumbai.g.alchemy.com/v2/Ksd4J1QVWaOJAJJNbr_nzTcJBJU-6uP3>",
    "tokenName": "Matic",
    "tokenSymbol": "MATIC"
 },
 {
    "name": "Avalanche",
    "chainId": 43113,
    "rpc": "<https://api.avax-test.network/ext/bc/C/rpc>",
    "gateway": "0xC249632c2D40b9001FE907806902f63038B737Ab",
    "tokenName": "Avax",
    "tokenSymbol": "AVAX"
 }
]
```

The JSON object includes the names of the two blockchains you’re deploying on, the unique chain ID for each blockchain, the remote procedure call ([RPC](https://www.alchemy.com/overviews/rpc-node)) needed to interact with the blockchains and the token info for each chain.

Let’s now install a few packages which you will use for your deployments. The first package is `dotenv` and the second is `axelar-local-dev`.

The `dotenv` package will simply allow you to write .env files that you do not want the whole world to see on GitHub. The `axelar-local-dev` package is released by Axelar. You will use it for deploying your contracts.

### **Hardhat config**

Now that you have your packages installed, you can begin writing up your `hardhat.config` file. Upon initialization, the Solidity version should be the only configuration that should be there.

The first thing you need is to import your `chains.json` file into the config file. Next, you will import your `dotenv` file and run:

```bash
dotenv.config()
```

You then need to set up the networks for the Polygon and Avalanche blockchains.

You will do this right under the Solidity language version:

```javascript
import dotenv from 'dotenv';
import { HardhatUserConfig } from 'hardhat/types';
import '@nomicfoundation/hardhat-toolbox';
import '@nomiclabs/hardhat-ethers';
import chains from './chains.json';
dotenv.config();
const config: HardhatUserConfig = {
    solidity: '0.8.18',
    networks: {
        polygon: {
            url: chains[0].rpc,
            accounts: [0x${process.env.PRIVATE_KEY}],
            network_id: 80001,
        },
        avalanche: {
            url: chains[1].rpc,
            accounts: [0x${process.env.PRIVATE_KEY}],
            network_id: 43113,
        },
    },
};
export default config;
```

For the url, you access the RPC from the `chains.json` file you imported earlier. The private key for your wallet should be set in your `.env` file. It should link back to an account with sufficient funds available to deploy your contracts on Polygon and Avalanche. Lastly, your network ID is the unique chain ID for each blockchain.

### **Deploy with a nonce script**

Now that you have your Hardhat config, you can deploy your *Lock* contract. Under the scripts folder, rename the existing `deploy.ts` to `deploy-with-nonce.ts.` Next, delete the existing deployment logic, as you will be rewriting your own logic with the `axelar-local-dev` package to deploy on multiple chains at once!

Your fresh script file should now look like this:

```javascript
import { ethers } from 'ethers';

async function main() {
    const currentTimestampInSeconds = Math.round(Date.now() / 1000);
    const unlockTime = currentTimestampInSeconds + 60;
    const lockedAmount = ethers.parseEther("0.001")
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
```

Now, let’s import your dependencies from the packages you installed earlier.

```javascript
import { Wallet, getDefaultProvider } from 'ethers';
import { deployContract } from '@axelar-network/axelar-local-dev';
import LockAbi from '../artifacts/contracts/Lock.sol/Lock.json';
import chains from '../chains.json';
```

Next, you want to add a helper function called `getEvmChains()` underneath your `main()` function, to access data from your chains.json file.

```bash
function getEvmChains() {
    return chains.map((chain) => ({ ...chain }));
}
```

You can now call this function in the `main()` function and loop through its returned value to access each chain

Your main function will now look like this:

```javascript
async function main() {
   const currentTimestampInSeconds = Math.round(Date.now() / 1000);
   const unlockTime = currentTimestampInSeconds + 60;
   const lockedAmount = ethers.parseEther("0.001")
   const evmChains = getEvmChains();
   for (const chain of evmChains) {}
}
```

Now, you can begin to write out the deployment logic for each chain.

For this you need to wire up your wallet. Once your wallet is wired up, you can finally call the `deployContract` function from your `axelar-local-dev` package.

And that’s it! You should now be able to deploy your *Lock* contract on both Polygon and Avalanche.

```javascript
async function main() {
   const currentTimestampInSeconds = Math.round(Date.now() / 1000);
   const unlockTime = currentTimestampInSeconds + 60;
   const evmChains = getEvmChains();
   const privateKey = process.env.PRIVATE_KEY;

   if (!privateKey) {
       throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.');
   }

   const wallet = new Wallet(privateKey);

   for (const chain of evmChains) {
       const provider = getDefaultProvider(chain.rpc);        
       const connectedWallet = wallet.connect(provider);
       const lockContract = await deployContract(connectedWallet, LockAbi, [unlockTime]);
       console.log(${chain.name} contract address: ${lockContract.address});
   }
}
```

Let’s review the code.

First, you access the private key, which is defined in the `.env` file. Then, you can initialize a new wallet with that private key. Now, in your loop, you can get the specific RPC endpoint for each blockchain you are deploying on. You then connect the wallet to the RPC provider.

For the deployment, you pass in `connectedWallet`, which is simply your wallet object with a wired-up provider. You then pass in the application binary interface ([ABI](https://en.wikipedia.org/wiki/Application_binary_interface)) of the *Lock* contract and `unlockTime`, which is the argument required by the *Lock* contract’s `constructor()`.

When you run the following command in your CLI:

```bash
hh run scripts/deploy-with-nonce.ts
```

You should see the following log:

```bash
Polygon contract address: "YOUR_ADDRESS"
Avalanche contract address: "YOUR_ADDRESS"
```

If you are receiving an error, please review the previous steps to try and see what went wrong. If you are deploying successfully but are not seeing the same address, then it's best to check what nonce each address is on. Recall what we mentioned at the beginning, that each address has its own nonce. Even though you are using the same wallet address to deploy the contract, they are still on different blockchains, so their nonces may be out of sync.

To check your nonce, you can go through each blockchain’s block explorer: in this case [Snowtrace](https://testnet.snowtrace.io/) and [Polygonscan](https://mumbai.polygonscan.com/). Just click on the most recent transaction hash for a given account, then click on see more you will see the nonce.

![Using this screenshot of polygonscan to show how to find nonce on the block explorer. For the Interchain constant address deployment post](https://axelar.network/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fmjqoq33y05of%2F5EHUpKHS3sVVfPyD36dyxX%2Fbe87cd27ce606c0d339efc1eb2762822%2Fnonce-on-explorer.png&w=3840&q=50 align="left")

You can also check in the Hardhat console.

Using the Hardhat console may require you to install `hardhat-ethers` as a dependency. To access the console, run:

#### **For Avalanche**

```bash
npx hardhat console --network avalanche
```

#### **For Polygon**

```bash
npx hardhat console --network polygon
```

#### **Completing the deployment**

Once in the console run the command

```bash
await ethers.provider.getTransactionCount("YOUR_ADDRESS")
```

If your nonces are out of sync, simply send transactions from the account that has fewer transactions, until both addresses are at the same nonce.

## **Part 2: Using Axelar *Create2Deployer***

What if you want to pass in a custom value to determine your address, rather than being restricted to your account’s nonce? Fortunately, Axelar has a live service at your disposal, which you can use to deploy contracts.

### **Deploy with *Create2Deployer***

[*Create2Deployer*](https://docs.axelar.dev/dev/general-message-passing/solidity-utilities) allows you to deploy contracts to the same address much like you're doing in the example above. The difference this time is, you are deploying not from your own contract but via a separate, pre-deployed contract that Axelar already has running on many blockchains.

Instead of needing to sync up the nonces between the Polygon and Avalanche blockchains from your own address, you will be using Axelar’s *Create2Deployer* contract, which will handle deployment using the [CREATE2 opcode](https://eips.ethereum.org/EIPS/eip-1014).

### **A small bit about Create2:**

CREATE2 is an opcode used to help determine the address of a deployed contract. The parameters it takes in to determine the address are the deployer’s address, a salt and the creation code of a smart contract. The creation code of the contract is the code used to set up the contract (in other words, runtime bytecode + constructor). What is really powerful here with CREATE2 is that you can now predict what the address will be before the address is even deployed.

The problem is that for each deployment of the contract, different parameters might be passed into the constructor, which would end up affecting the address of the deployed contract. For example, passing the number 40 to the constructor on Polygon and the number 30 to the constructor on Avalanche will lead to a different address on each blockchain.

The solution for this is to remove your constructor in favor of an initializer. For clarity, let’s make a new contract to showcase this and call it *LockCreate2*.

```bash
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.18;
// Uncomment this line to use console.log
// import "hardhat/console.sol";

contract LockCreate2 {
   uint public unlockTime;
   address payable public owner;

   event Withdrawal(uint amount, uint when);

   function initialize(uint256 _unlockTime, address _owner) public payable {
       require(block.timestamp < _unlockTime, 'Unlock time should be in the future');
       unlockTime = _unlockTime;
       owner = payable(_owner);
   }

   function withdraw() public {
       require(block.timestamp >= unlockTime, "You can't withdraw yet");
       require(msg.sender == owner, "You aren't the owner");
       emit Withdrawal(address(this).balance, block.timestamp);
       owner.transfer(address(this).balance);
   }
}
```

You can see this contract is identical to the *Lock* contract, except you use an initializer as opposed to a constructor. In production you would also be importing an [initializable contract from OpenZeppelin.](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol) What this will do is lock the initialize function, so that it can only be called once on deployment (just like a constructor - [see here](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol)).

Let’s now create a new script to go alongside your original deploy-with-nonce script. You’ll call this second script `deploy-create2-deployer.ts .`

Now, you can start with your imports:

```javascript
import { Wallet, getDefaultProvider, BigNumber, ethers } from 'ethers';
import Lock from '../artifacts/contracts/LockCreate2.sol/LockCreate2.json';
import Create2Deployer from '@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/deploy/Create2Deployer.sol/Create2Deployer.json';
import chains from '../chains.json';
```

Next, you can get the address of the live *Create2Deployer* contract. It is the same across all EVM chains. You can simply set it as a variable under your import statements.

```javascript
const CREATE2_DEPLOYER_ADDR = '0x98b2920d53612483f91f12ed7754e51b4a77919e';
```

For the functionality, you begin by defining an empty `main()` function.

You can also add the `getEvmChains()` function, exactly as you had for your previous script.

At this point, your code should look like this:

```javascript
import { Wallet, getDefaultProvider, BigNumber, ethers } from 'ethers';
import Lock from '../artifacts/contracts/LockCreate2.sol/LockCreate2.json';
import Create2Deployer from '@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/deploy/Create2Deployer.sol/Create2Deployer.json';
import chains from '../chains.json';

const CREATE2_DEPLOYER_ADDR = '0x98b2920d53612483f91f12ed7754e51b4a77919e';

async function main() {}

function getEvmChains() {
   return chains.map((chain) => ({ ...chain }));
}
main().catch((error) => {
   console.error(error);
   process.exitCode = 1;
});
```

In your main function, you can call the private key from your env variable as well as your `getEvmChains()` function. You can then set up your loop of the EVM chains. In the loop, you can wire up your wallet as you did before.

```bash
async function main() {
   const privateKey = process.env.PRIVATE_KEY;
  
   if (!privateKey) {
       throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.');
   }
  
   const evmChains = getEvmChains();
   
   for (const chain of evmChains) {
      const wallet = new Wallet(privateKey);
      const provider = getDefaultProvider(chain.rpc);
      const connectedWallet = wallet.connect(provider);
   }
}
```

Everything up until this point should look quite familiar. Now, you want to use Axelar’s *Create2Deployer* to deploy your contract. To interact with a live, on-chain contract, you need both the address and the ABI. Fortunately, you have both of those.

```bash
const deployerContract = new ethers.Contract(CONST_ADDRESS_DEPLOYER_ADDR, Create2Deployer.abi, connectedWallet);
```

Now that you have access to the *Deployer* contract, you can start interacting with its functionality. Since you’re going to be deploying a contract with an initializer, you are going to be calling the `deployAndInit()` function on the *Deployer* contract. (The *Deployer’s* complete functionality can be found [here](https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/main/contracts/deploy/ConstAddressDeployer.sol).)

```javascript
function deployAndInit(
   bytes memory bytecode,
   bytes32 salt,
   bytes calldata init
) external returns (address deployedAddress_)
{}
```

The parameters are the bytecode of the contract you’re deploying, a unique salt and an encoded initialize function.

Before you trigger it, let’s set up the encoded initialize function.

```javascript
function encodeInitData(wallet) {
   const currentTimestampInSeconds = Math.round(Date.now() / 1000);
   const unlockTime = currentTimestampInSeconds + 60;
   
   // Encode the function call
   const initFunction = 'initialize(uint256,address)';
   const initData = ethers.utils.defaultAbiCoder.encode(['uint256', 'address'], [unlockTime, wallet.address]);
   const initSignature = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(initFunction)).slice(0, 10);   // Remove 0x
   return initSignature + initData.substring(2);
}
```

In this function, you return an encoded version of the initialize function defined in your *LockCreate2* contract. You encode the function signature and the value you want passed into the function. Note: in addition to the `unlockTime` you're also passing in the `wallet.address` as the owner is passed in as a param instead of just passing in the `msg.sender`. The reason for this is because the `msg.sender` would be the `Create2Service` itself. To avoid losing ownership of the contract you simply pass in the `_owner` as a hardcoded address.

You can call this function in your `main()` function, right beside where you call the `getEvmChains()` function.

Next, you need to get your unique salt, which you will pass into the `deployAndInit()` function.

The salt can be any number that you convert to a `bytes32` data type.

It can be done as follows:

```bash
const salt = ethers.utils.hexZeroPad(BigNumber.from(99), 32);
```

Here you are encoding the number 99 to a `bytes32`compatible value.

Now that you have your salt and your encoded *init* parameters, you can call the `deployAndInit()` function.

```bash
const deployedAddr = await deployerContract.deployAndInit(Lock.bytecode, salt, initData);
```

Now, you can get the transaction receipt and log out the deployed address from the event logs.

At this point, the complete script should be as follows.

```javascript
import { Wallet, getDefaultProvider, BigNumber, ethers } from 'ethers';
import Lock from '../artifacts/contracts/LockCreate2.sol/LockCreate2.json';
import Create2Deployer from '@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/deploy/Create2Deployer.sol/Create2Deployer.json';
import chains from '../chains.json';

const CREATE2_DEPLOYER_ADDR = '0x98b2920d53612483f91f12ed7754e51b4a77919e';

async function main() {
   const privateKey = process.env.PRIVATE_KEY;
   if (!privateKey) {
      throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.');
   }


   const evmChains = getEvmChains();

   for (const chain of evmChains) {
      const wallet = new Wallet(privateKey);
      const initData = encodeInitData(wallet);
      const provider = getDefaultProvider(chain.rpc);
      const connectedWallet = wallet.connect(provider);
      const deployerContract = new ethers.Contract(CREATE2_DEPLOYER_ADDR, Create2Deployer.abi, connectedWallet);

      //salt (make sure this salt has not been used already)
      const salt = ethers.utils.hexZeroPad(BigNumber.from(201), 32);
      const deployedAddr = await deployerContract.deployAndInit(Lock.bytecode, salt, initData);
      const receipt = await deployedAddr.wait();

      console.log(${chain.name}, address: ${receipt.events[0].args.deployedAddress});
   }
}

function encodeInitData() {
   const currentTimestampInSeconds = Math.round(Date.now() / 1000);
   const unlockTime = currentTimestampInSeconds + 60;

   // Encode the function call
   const initFunction = 'initialize(uint256,address)';
   const initData = ethers.utils.defaultAbiCoder.encode(['uint256', 'address'], [unlockTime, wallet.address]);
   const initSignature = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(initFunction)).slice(0, 10);

   // Remove 0x
   return initSignature + initData.substring(2);
}

function getEvmChains() {
   return chains.map((chain) => ({ ...chain }));
}

main().catch((error) => { 
   console.error(error);
   process.exitCode = 1;
});
```

***Note: Because you deployed an address with the salt of 99, you can no longer use the same salt. The next deployment will need a new salt or there will be an error.***

The resulting log should be:

```bash
Polygon address: "YOUR_ADDRESS"
Avalanche address: "YOUR_ADDRESS"
```

Awesome! You have now used CREATE2 via Axelar *Create2Deployer* to deploy on both Avalanche and Polygon in just a single script execution.

## **Part 3: Using Axelar *Create3Deployer***

Leveraging *Create2Deployer* to preconfigure a deployed address is very useful, but the inability to deploy on multiple chains with different constructor arguments can be a nuisance. To allow for maximum flexibility, the Axelar team released the *Create3Deployer* contract. The *Create3Deployer* integrates [CREATE3](https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/main/contracts/deploy/Create3.sol), which allows users to deploy contracts independent of a contract’s bytecode. This solves the problem discussed earlier regarding different constructor parameters resulting in different addresses across blockchains. Now, rather than needing to use an initializer function to simulate a constructor, you can use a constructor directly, even if you have different parameter values being passed in. You can create a new contract called *LockCreate3* contract with the constructor instead of the initializer:

```javascript
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;
// Uncomment this line to use console.log
// import "hardhat/console.sol";

contract LockCreate3 {
   uint public unlockTime;
   address payable public owner;
   
   event Withdrawal(uint amount, uint when);

   constructor(uint256 _unlockTime, address _owner) payable {
      require(block.timestamp < _unlockTime, 'Unlock time should be in the future');
      unlockTime = _unlockTime;
      owner = payable(msg.sender);
   }

   function withdraw() public {
      require(block.timestamp >= unlockTime, "You can't withdraw yet");
      require(msg.sender == owner, "You aren't the owner");

emit Withdrawal(address(this).balance, block.timestamp);

      owner.transfer(address(this).balance);
   }

}
```

You'll notice this contract is nearly identical to the original Lock contract but now you pass an additional parameter called `_owner` to the constructor rather than using the `msg.sender`. This is because the `Create3Deployer` will be the `msg.sender` so you want to make sure you are not passing the ownership role to the `Create3Deployer`

In your deploy script, you need to pass in the address of your *Create3Deployer* rather than your *Create2Deployer*. The *Create3Deployer* is located at: 0x6513Aedb4D1593BA12e50644401D976aebDc90d8 across all chains connected to Axelar.

Now, rather than calling `deployAndInit()` as you did with *Create2Deployer,* you can simply call the `deploy()` function on the *Create3Deployer*. When calling `deploy()`, you need to pass in a unique salt value, as you did before with *Create2Deployer*; you also need to pass in an encoding of the contract’s bytecode and the arguments for your constructor. In this case, you need to pass in the `unlockTime` amount. You encode these two values with *ethersjs* in your *deploy* script. As follows:

```bash
const creationCode = ethers.utils.solidityPack( ['bytes', 'bytes'], [Lock.bytecode, ethers.utils.defaultAbiCoder.encode(['uint256', 'address'], [unlockTime, wallet.address])]);
```

You then can pass in your creationCode to the `deploy()` function along with the salt.

When you run the `deploy()` function you should now be able to deploy with the *Create3Deployer* contract! To view the address, however, you will not be able to use the transaction receipt as no event gets emitted from the `deploy()` function this time. The `deploy()` function does however return the address of the newly created contract so you can access the address that way. When calling the `deploy()` function, simply run `callStatic` before triggering the function. This will tell the node to simulate a call and return the result. You can now use the output of the function to log the address. Your complete script should now look like this:

```javascript
import { Wallet, getDefaultProvider, BigNumber, ethers } from 'ethers';
import Lock from '../artifacts/contracts/LockCreate3.sol/LockCreate3.json';
import Create3Deployer from '@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/deploy/Create3Deployer.sol/Create3Deployer.json';
import chains from '../chains.json';

const CREATE_3_DEPLOYER = '0x6513Aedb4D1593BA12e50644401D976aebDc90d8';

async function main() {
   const privateKey = process.env.PRIVATE_KEY;

   if (!privateKey) {
      throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.');
   }

   const currentTimestampInSeconds = Math.round(Date.now() / 1000);
   const unlockTime = currentTimestampInSeconds + 60;
   const evmChains = getEvmChains();

   for (const chain of evmChains) {
      const wallet = new Wallet(privateKey);
      const provider = getDefaultProvider(chain.rpc);
      const connectedWallet = wallet.connect(provider);
      const deployerContract = new ethers.Contract(CREATE_3_DEPLOYER, Create3Deployer.abi, connectedWallet);
      const salt = ethers.utils.hexZeroPad(BigNumber.from(101), 32);
      const creationCode = ethers.utils.solidityPack( ['bytes', 'bytes'], [Lock.bytecode, ethers.utils.defaultAbiCoder.encode(['uint256', 'address'], [unlockTime, wallet.address])]
   );

   const deployedAddress = await deployerContract.callStatic.deploy(creationCode, salt);

   console.log(${chain.name}, address: ${deployedAddress});
  }
}
```

## **Conclusion**

We have covered three different methods to deploy the same contract at the same address on two different blockchains. The first deployed from the same address with the same nonce on two different blockchains using the `axelar-local-dev` package. The second used the Axelar *Create2Deployer* contract. This contract uses the CREATE2 opcode with a unique salt, so you can have a higher degree of customization as to what your address will be.

The final method is by using The *Create3Deployer* contract, which uses the CREATE3 library to allow you to use a constructor even if the parameters passed in are different across several blockchains. You will also know the address you are about to deploy before you actually deploy on-chain.

The complete code can be found here: [https://github.com/axelarnetwork/interchain-deployment-same-address](https://github.com/axelarnetwork/interchain-deployment-same-address).
