Skip to content
On this page

Root Deployer

In this section we will learn what is the root deployer contract, its code and how to deploy it using tools like locklift or everscale-inpage-provider.

What is the Root Deployer ?

We have developed a smart contract written in t-solidity that facilitates the deployment of the TokenRoot contract and enables us to obtain the address of an already deployed TokenRoot contract.

Deploying a token root contract using this smart contract is easier than deploying it through an Account. Contributors can also customize the contract to suit different needs, such as keeping track of all deployed token roots or implementing other functionalities.

Please note that while the RootDeployer contract acts as the deployer of the TokenRoot contract, the ownership of the TokenRoot can be specified by the user.

WARNING

To retrieve the address of your deployed token root via the root deployer's getExpectedTokenRootAddress function, it is crucial to remember what was your token root randomNonce_ value!

Contract Code

show code
solidity
pragma ever-solidity >= 0.61.2;
pragma AbiHeader expire;
pragma AbiHeader pubkey;

import "@broxus/contracts/contracts/libraries/MsgFlag.tsol";

import "@broxus/tip3/contracts/TokenRoot.tsol";

contract RootDeployer {

    uint32 static randomNonce_;

    address owner_;

    TvmCell rootCode_;
    TvmCell walletCode_;

    constructor(
        TvmCell _rootCode,
        TvmCell _walletCode
    ) public {
        tvm.accept();
        owner_ = msg.sender;
        rootCode_ = _rootCode;
        walletCode_ =  _walletCode;
    }

    function getExpectedTokenRootAddress(
        string name,
        string symbol,
        uint8 decimals,
        address rootOwner,
        uint32 randomNonce
        ) public view returns(address){
        return address(tvm.hash(tvm.buildStateInit({
            contr: TokenRoot,
            varInit: {
                randomNonce_: randomNonce,
                deployer_: address(this),
                rootOwner_: rootOwner,
                name_: name,
                symbol_: symbol,
                decimals_: decimals,
                walletCode_: walletCode_
            },
            pubkey: 0,
            code: rootCode_
        })));
    }


    function DeployRootDeployer(
        string name,
        string symbol,
        uint8 decimals,
        address initialSupplyTo,
        uint128 initialSupply,
        uint128 deployWalletValue,
        address rootOwner,
        uint32 randomNonce,
        bool mintDisabled,
        bool burnByRootDisabled,
        bool burnPaused,
        address remainingGasTo
    ) public view{
        tvm.accept();
        TvmCell initData = tvm.buildStateInit({
            contr: TokenRoot,
            varInit: {
                randomNonce_: randomNonce,
                deployer_: address(this),
                rootOwner_: rootOwner,
                name_: name,
                symbol_: symbol,
                decimals_: decimals,
                walletCode_: walletCode_
            },
            pubkey: 0,
            code: rootCode_
        });

        new TokenRoot {
            stateInit: initData,
            value: 2 ever,
            flag: MsgFlag.SENDER_PAYS_FEES
        }(
            initialSupplyTo,
            initialSupply,
            deployWalletValue,
            mintDisabled,
            burnByRootDisabled,
            burnPaused,
            remainingGasTo
        );
    }
}

TIP

It is important to understand that smart contracts in Everscale have a life of their own

live in separate mini-blockchains and can only communicate by messages.

For this reason, we pass callbacks so that the contract returns something.

Step 1: Write Deployment Script

Now we write scripts to deploy the Root Deployer contract :

We have already covered how to deploy a contract using locklift, so deploying RootDeployer should be a straightforward process:

INFO

Before we start to write our scripts we need to make sure that there is file named 01-deploy-root-deployer.ts in the script folder in the project root.

We have already learned how to deploy a contract using everscale-inpage-provider as well, so the process remains the same as before.


typescript
/**
 * locklift is a globally declared object
 */

import {
  Address,
  Contract,
  Signer,
  WalletTypes,
  zeroAddress,
} from 'locklift';
import { FactorySource } from '../build/factorySource';

async function getWalletData(
  MWContract: Contract<FactorySource['MultiWalletTIP3']>,
  tokenRootAddress: Address
): Promise<{ tokenWallet: Address; balance: number }> {
  // Returned value of the wallets mapping on the multi wallet tip-3 contract
  const walletData = (
    await MWContract.methods.wallets().call()
  ).wallets.map(item => {
    if (item[0].toString() == tokenRootAddress.toString()) {
      return item[1];
    }
  });
  let balance: number = 0;
  let tokenWallet: Address = zeroAddress;
  if (walletData.length != 0) {
    balance = Number(walletData[0]!.balance);
    tokenWallet = walletData[0]!.tokenWallet;
  }
  return { tokenWallet: tokenWallet, balance: balance };
}
async function main() {
  // Fetching the signer key pair from locklift.config.ts
  const signerAlice: Signer =
    (await locklift.keystore.getSigner('0'))!;
  const signerBob: Signer = (await locklift.keystore.getSigner('1'))!;

  // uncomment if deploying a new account
  // const { contract: account } = await locklift.factory.deployContract({
  //   contract: "Account",
  //   publicKey: signer.publicKey,
  //   constructorParams: {},
  //   initParams: { _randomNonce: locklift.utils.getRandomNonce() },
  //   value: locklift.utils.toNano(20),
  // });

  // Adding an existing SafeMultiSig Account using its address
  const aliceAccount =
    await locklift.factory.accounts.addExistingAccount({
      type: WalletTypes.MsigAccount,
      address: new Address('<ALICE_ACCOUNT_ADDRESS>'),
      mSigType: 'SafeMultisig',
      publicKey: signerAlice.publicKey,
    });

  // uncomment if deploying a new account
  // const { contract: account } = await locklift.factory.deployContract({
  //   contract: "Account",
  //   publicKey: signer.publicKey,
  //   constructorParams: {},
  //   initParams: { _randomNonce: locklift.utils.getRandomNonce() },
  //   value: locklift.utils.toNano(20),
  // });

  // Adding an existing SafeMultiSig Account using its address
  const bobAccount =
    await locklift.factory.accounts.addExistingAccount({
      type: WalletTypes.MsigAccount,
      address: new Address('<BOB_ACCOUNT_ADDRESS>'),
      mSigType: 'SafeMultisig',
      publicKey: signerBob.publicKey,
    });

  // Deploying the Root deployer
  const { contract: rootDeployer } =
    await locklift.factory.deployContract({
      contract: 'RootDeployer',
      publicKey: signerAlice.publicKey,
      initParams: {
        randomNonce_: locklift.utils.getRandomNonce(),
      },
      constructorParams: {
        _rootCode:
          locklift.factory.getContractArtifacts('TokenRoot').code,
        _walletCode:
          locklift.factory.getContractArtifacts('TokenWallet').code,
      },
      value: locklift.utils.toNano('5'),
    });

  console.log(`Root Deployer: ${rootDeployer.address.toString()}`);
}

main()
  .then(() => process.exit(0))
  .catch(e => {
    console.log(e);
    process.exit(1);
  });
typescript
import {
  ProviderRpcClient as PRC,
  Address,
  GetExpectedAddressParams,
  Contract,
  Transaction,
  FullContractState,
} from 'everscale-inpage-provider';
import * as tip3Artifacts from 'tip3-docs-artifacts';
import { provider, providerAddress } from './useProvider';

async function main() {
  // Root Deployer contract abi
  const rootDeployerAbi: tip3Artifacts.FactorySource['RootDeployer'] =
    tip3Artifacts.factorySource['RootDeployer'];

  // required contracts code and tvc
  const tokenRootArtifacts: typeof tip3Artifacts.artifacts.TokenRoot =
    tip3Artifacts.artifacts.TokenRoot;
  const tokenWalletArtifacts: typeof tip3Artifacts.artifacts.TokenWallet =
    tip3Artifacts.artifacts.TokenWallet;
  const rootDeployerArtifacts: typeof tip3Artifacts.artifacts.RootDeployer =
    tip3Artifacts.artifacts.RootDeployer;

  // define the deployParams type
  type DeployParams<Abi> = GetExpectedAddressParams<Abi> & {
    publicKey: string | undefined;
  };

  // Fetching the user public key
  const accountFullState: FullContractState = (
    await provider.getFullContractState({ address: providerAddress })
  ).state!;

  const senderPublicKey: string = await provider.extractPublicKey(
    accountFullState.boc
  );

  // Preparing the deployment params
  const deployParams: DeployParams<
    tip3Artifacts.FactorySource['RootDeployer']
  > = {
    tvc: rootDeployerArtifacts.tvc,
    workchain: 0,
    publicKey: senderPublicKey,
    initParams: {
      randomNonce_: (Math.random() * 6400) | 0,
    },
  };

  // Get the expected address of the root deployer contract
  const expectedAddress = await provider.getExpectedAddress(
    rootDeployerAbi,
    deployParams
  );

  // Get the state init
  const stateInit = await provider.getStateInit(
    rootDeployerAbi,
    deployParams
  );

  // Send the coins to the address
  await provider.sendMessage({
    sender: providerAddress,
    recipient: expectedAddress,
    amount: String(3 * 10 ** 9),
    bounce: false, // It is important to set 'bounce' to false
    // to ensure funds remain in the contract.
    stateInit: stateInit.stateInit,
  });

  console.log('Fund sent to the Calculated address !');

  // Create a instance of the root deployer contract
  const userRootDeployer: Contract<
    tip3Artifacts.FactorySource['RootDeployer']
  > = new provider.Contract(rootDeployerAbi, expectedAddress);

  console.log('Sending stateInit to the Calculated address ...');

  // Call the contract constructor
  const { transaction: deployRes } = await userRootDeployer.methods
    .constructor({
      _rootCode: tokenRootArtifacts.code,
      _walletCode: tokenWalletArtifacts.code,
    })
    .sendExternal({
      stateInit: stateInit.stateInit,
      publicKey: deployParams.publicKey!,
    });

  // checking if the token root is deployed successfully by calling one of its methods
  if (
    (
      await provider.getFullContractState({
        address: expectedAddress,
      })
    ).state?.isDeployed
  ) {
    console.log(`Root Deployer deployed successfully`);
    return `Root Deployer deployed to ${expectedAddress.toString()}`;
  } else {
    throw new Error(
      `Root Deployer deployment failed !${
        (deployRes.exitCode, deployRes.resultCode)
      }`
    );
  }
}

Step 2: Deploy Root Deployer

Let's run our script using locklift

shell
npx locklift run -s ./scripts/01-deploy-root-deployer.ts -n local
deployRootDeployerOutput

Congratulations, you have deployed a Root Deployer contract 🎉

GIF